What is a Pure Function?
Let’s start by testing pure functions. A pure function is a special kind of function that:
Always gives you the same output when you give it the same input For example: A function that adds two numbers will always give you 4 when you input 2 and 2
Only works with the data you give it It doesn’t check anything else (like what’s in localStorage or what’s on the webpage)
Doesn’t change anything outside the function It just takes some input and returns something new
Here’s a simple example of a pure function:
function addNumbers(a, b) {
return a + b;
}
Pure functions are perfect for learning how to write tests because:
- They’re predictable (same input always gives same output)
- They’re easy to test (you just need to check if the output is correct)
- They don’t depend on anything else in your program
For this part of the lesson, we’ll be using examples from this GitHub repository:
https://github.com/NoroffFEU/workflow-repo
The functions we’ll be testing are located in the src/js/utils/
directory of the repository.
Testing the validateEmail function
You can find this function in the src/js/utils/validation.js
file:
export function validateEmail(email) {
const emailRegex = /^[^\s@]+@(stud\.noroff\.no|noroff\.no)$/;
return emailRegex.test(email);
}
This function checks if an email address belongs to a Noroff student or staff member. Here is what it does:
- If the email ends with “stud.noroff.no”, it’s a valid student email
- If it ends with “noroff.no”, it’s a valid staff email
- Any other email address is not valid
For example:
- ”student@stud.noroff.no” ✅ (valid student email)
- ”teacher@noroff.no” ✅ (valid staff email)
- ”student@gmail.com” ❌ (not valid - wrong domain)
Now, let’s write tests for this function in validation.test.js
:
import { expect, describe, it } from "vitest";
import { validateEmail } from "./validation";
describe("validateEmail", () => {
// Test 1: Make sure student emails work
it("returns true for valid student Noroff email", () => {
const email = "student@stud.noroff.no";
const result = validateEmail(email);
expect(result).toBe(true);
});
// Test 2: Make sure staff emails work
it("returns true for valid Noroff staff email", () => {
const email = "teacher@noroff.no";
const result = validateEmail(email);
expect(result).toBe(false);
});
// Test 3: Make sure other email domains are rejected
it("returns false for non-Noroff email", () => {
const email = "student@gmail.com";
const result = validateEmail(email);
expect(result).toBe(false);
});
// Test 4: Make sure invalid email formats are rejected
it("returns false for invalid email format", () => {
const email = "not-an-email";
const result = validateEmail(email);
expect(result).toBe(false);
});
});
In these tests:
- We group related tests using
describe
. - Each
it
function describes a specific behavior we’re testing. - We follow the Arrange-Act-Assert pattern in each test:
- Arrange: Set up the email to test
- Act: Call the validateEmail function
- Assert: Check if the result is what we expect
This approach helps us thoroughly test our validateEmail
function for different scenarios.
Testing the validatePassword function
Now let’s test the validatePassword
function. Here’s the function in validation.js
:
export function validatePassword(password) {
return password.length >= 8;
}
And here are the tests in validation.test.js
:
import { validatePassword } from "./validation";
describe("validatePassword", () => {
const testCases = [
{ password: "short", expected: false },
{ password: "exactly8", expected: true },
{ password: "longerpassword", expected: true },
];
testCases.forEach(({ password, expected }) => {
it(`returns ${expected} for password "${password}"`, () => {
const result = validatePassword(password);
expect(result).toBe(expected);
});
});
});
In these tests:
- We define an array of test cases, each with a password and the expected result.
- We use
forEach
to run the same test for each case. - The test description changes based on the password and expected result.
- We check if the function returns the expected result for each password.
This approach allows us to test multiple scenarios efficiently.
Testing the validateForm function
Finally, let’s test the validateForm
function. Here’s the function in validation.js
:
export function validateForm(email, password) {
const errors = {};
if (!validateEmail(email)) {
errors.email = "Please enter a valid Noroff email address";
}
if (!validatePassword(password)) {
errors.password = "Password must be at least 8 characters";
}
return {
isValid: Object.keys(errors).length === 0,
errors,
};
}
And here are the tests in validation.test.js
:
import { validateForm } from "./validation";
describe("validateForm", () => {
// We're testing three different situations:
const testCases = [
{
// Situation 1: Everything is correct
email: "valid@stud.noroff.no",
password: "validpass",
expected: { isValid: true, errors: {} },
},
{
// Situation 2: Everything is wrong
email: "invalid@gmail.com",
password: "short",
expected: {
isValid: false,
errors: {
email: "Please enter a valid Noroff email address",
password: "Password must be at least 8 characters",
},
},
},
{
// Situation 3: Email is good but password is too short
email: "valid@noroff.no",
password: "short",
expected: {
isValid: false,
errors: {
password: "Password must be at least 8 characters",
},
},
},
];
testCases.forEach(({ email, password, expected }) => {
it(`validates correctly for email "${email}" and password "${password}"`, () => {
const result = validateForm(email, password);
expect(result).toEqual(expected);
});
});
});
In these tests:
- We define test cases with different combinations of email and password.
- Each case includes the expected result (whether the form is valid and any error messages).
- We use
forEach
to run the same test structure for each case. - We use
toEqual
instead oftoBe
because we’re comparing objects.
This approach tests the validateForm
function thoroughly, checking how it handles various combinations of valid and invalid inputs.
What We Learned
In this lesson, we:
- Understood what pure functions are and why they’re good for testing
- Learned how to test email validation
- Practiced testing password validation
- Created tests for form validation that returns objects with error messages
Remember:
- Test both valid and invalid inputs
- Test all possible scenarios (like our three form validation cases)
- Use clear test descriptions that explain what you’re testing
- Group related test cases together