Skip to main content

Introducing Unit Testing for Beginner Devs

Weslley AraΓΊjo
Active open-source contributor, Microsoft MVP, MySQL2's maintainer and Poku's creator ✨
LinkedInGitHubInstagramYouTube


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');
Assertion error
  • actual:
    • "11" β€” the dynamic return (soft code) from our function or variable
  • expected:
    • 2 β€” the hard-coded value (hard code) that actual should match

Both Poku and Node.js provide an assert API 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');
Successful assertion

πŸ§ͺ 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 be false.
  • βœ… 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 be true.
  • πŸ“ 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:

Success Example with Poku
  • 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:
    • βœ… 0 for success
    • ❌ 1 for 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 πŸ§‘πŸ»β€πŸŽ“
info

This article was initially posted on TabNews.