Introducing Unit Testing for Beginner Devs

Summary
If you're a beginner developer (or not), I want to show you that testing can be simple and that complexity only comes according to our needs.
π§π»βπ Introduction to the World of Testingβ
Imagine a system where several users had their accounts compromised because they created passwords like "1234" and the system allowed it, even though there was validation for that β but with no guarantee the function was actually working π₯²
This could easily be avoided if there were tests ensuring the password validation function worked as it should whenever a change is made to the system, right? π
But when a beginner developer looks for automated testing, it can seem (or even be) complex:
- Testers may require environment configurations (especially around ESM and TypeScript)
- They can change the behavior of your development environment
- They may require you to adapt your code to work with them
- And they may demand prior, in-depth knowledge (documentation) just to get started
π Choosing the Testerβ
There are many testers today β some focused on best practices, others on productivity, performance, and so on.
Some of the most popular:
For this tutorial, we'll use a tester I created (Poku) because of its simplicity, but you can use any tester you prefer π
π¦ Installing our Testerβ
npm i -D poku
π§ͺ Why "Unit"?β
When we develop code, it's common to split small tasks into smaller functions and export those functions to be used in multiple places.
Creating unit tests ensures those "unit functions" always behave as expected π§π»
π Creating a Simple Projectβ
Let's build a very simple project where we'll only validate a password. Our project needs to check that the provided password:
- β Is a string
- β Has at least eight characters
- β Contains at least one uppercase letter
- β Contains at least one lowercase letter
- β Contains at least one number
If the password is valid, we should return true; otherwise, false.
To implement this, create the file:
- src/validations.mjs
export const validatePassword = (password) => {
// Checks if the password is a string
if (typeof password !== 'string') return false;
// Checks if the password has 8 characters or more
if (password.trim().length < 8) return false;
// Checks if the password has at least one uppercase letter
if (!/[A-Z]/.test(password)) return false;
// Checks if the password has at least one lowercase letter
if (!/[a-z]/.test(password)) return false;
// Checks if the password has at least one number
if (!/[0-9]/.test(password)) return false;
// Returns true if all validations above pass
return true;
};
To keep the example simple, we won't go much further than this π§π»
- Don't use this function in production; it's intentionally simple to focus on learning π§π»βπ
- You can change from src/validations.mjs to src/validations.js by adding
"type": "module"to your package.json π§π»
π§π»βπ¬ Understanding Testsβ
Theoretically, our function already works β this is usually when people start sprinkling console.log calls around, right? Don't do that π
Let's turn those ad-hoc console.logs into automated tests that will run whenever the project changes π
How? Using assertions ππ»
βοΈ What Are Assertions?β
In tests, assertions are used to ensure a result is exactly what we expect.
Each tester may provide a different API, but the idea is the same:
- If our check (assertion) does not match the expectation, the test fails with an error.
π Example:β
Imagine 1 + 1 should return 2, but it returned "11".
When reaching the assertion that expects 2, the test will fail and show that it expected 2 (a number) but received "11" (a string):
import { assert } from 'poku';
assert.strictEqual('1' + '1', 2, '1+1 should return 2');
actual:- "11" β the dynamic return (soft code) from our function or variable
expected:- 2 β the hard-coded value (hard code) that
actualshould match
- 2 β the hard-coded value (hard code) that
Both Poku and Node.js provide an
assertAPI with similar usage π§π»Functionally, Poku offers a simple and smart way to run multiple files and prints a clear report of passed and failed assertions in the terminal π·
π Fixing Our Example:β
import { assert } from 'poku';
assert.strictEqual(1 + 1, 2, '1+1 should return 2');
π§ͺ Creating the Testsβ
Finally β the fun part π
We'll be a bit creative and generate:
- β Several invalid passwords to simulate both typical users and malicious attempts
- For invalid passwords we expect (
expected) the result (actual) to befalse.
- For invalid passwords we expect (
- β
Valid passwords to make sure our function recognizes valid inputs when all criteria are met
- For valid passwords we expect (
expected) the result (actual) to betrue.
- For valid passwords we expect (
- π No extra comments in the test β we'll describe each case directly in the assertion message
Create the test file:
- test/password.test.mjs
import { assert } from 'poku';
import { validatePassword } from '../src/validations.mjs';
assert.strictEqual(
validatePassword(),
false,
'Validates if the password is not provided'
);
assert.strictEqual(
validatePassword(12345678),
false,
'Validates if the password is not a string'
);
assert.strictEqual(
validatePassword(''),
false,
'Validates if the password is an empty string'
);
assert.strictEqual(
validatePassword('abcd1234'),
false,
'Validates if the password does not have at least one uppercase letter'
);
assert.strictEqual(
validatePassword('1234EFGH'),
false,
'Validates if the password does not have at least one lowercase letter'
);
assert.strictEqual(
validatePassword('abcdEFGH'),
false,
'Validates if the password does not have at least one number'
);
assert.strictEqual(
validatePassword('abcdEF12'),
true,
'Validates if the password is valid'
);
- You can switch from test/password.test.mjs to test/password.test.js by setting
"type": "module"in your package.json π§π»
π¬ Checking If the Tests Passedβ
npx poku
- When you run
npx poku, by default the tester will look for files with the*.test.*or*.spec.*extension in the current directory π§π»
And finally, the result:
- The first line shows the directory where Poku is searching for tests
- The second line shows the file currently being tested
- Inside each test file it lists all assertions that include a message
- When finished (success or failure), it shows the command it ran for that file:
node test/password.test.mjs
- When all test files finish, it shows a summary of how many files passed and/or failed
- The exit code will be:
- β
0for success - β
1for failure
- β
π Conclusionβ
With the unit tests we created, we ensure not only that our function behaves as expected, but also that we can anticipate how our project reacts in unusual situations before they occur π
I hope I've shown that tests can indeed be simple π§π»βπ
- This was my first "lesson" in blog format. Feedback is always welcome π©΅
- Many terms are explained multiple times by choice for didactic purposes β let me know if it felt repetitive π§π»βπ
This article was initially posted on TabNews.