(This Blog post is part of a collaborative work between Me and Mustapha El Idrissi, Consult his devTo page for more information: https://dev.to/appsbymuss)
Definition
Tests are automated procedures or scripts that evaluate whether a part of a program or system works as expected. Testing is essential in ensuring the correctness, quality, and reliability of software by catching bugs or issues early in the development process.
Many developers skip writing tests altogether, but if you're developing an IT solution, then writing tests is a very crucially necessary step.
When you write code without writing any tests, the problems won't arise at the beginning, but they will emerge when you make modifications to a codebase that is tightly coupled with multiple parts of the system/app, now that would be quite painful to fix since you might not even acknowledge the problem your tiny change caused to another part of the app until it's too late (aka in Production), I have learned this the hard way.
My Personal Experience with developing untested code
In the past, I've always avoided writing test code for parts of my code because I felt like it was a waste of time, that was until I was tasked to develop the backend of a mid-size website that has too many moving parts.
My code worked for the most part until I began to see weird edge case errors that would only appear at seemingly random moments then they disappeared, that made frustrated since I couldn't put my finger on what exactly was faulty in my code.
The solution was that I began to learn what TDD (Testing-Driven Development) is, and decided to write test code for my app, I didn't even cover most of the code but even that little test code I had made me uncover code that had bugs, one of them was a race condition bug (which by the way, would have been almost impossible to successfully debug without these tests)
What are tests designed for ?
- Verify functionality: Ensure that the software behaves as expected under normal and edge-case conditions.
- Identify bugs or regressions: Detect defects introduced during development or code changes.
- Measure performance: Check whether the system performs efficiently under various conditions.
- Validate requirements: Confirm that the software meets the specified requirements or user expectations. ## Writing tests In order to write tests one must first understand the types of Tests there are:
in general there are 3 types of tests:
Unit Tests: these usually test pure functions that don't have any API or Database interaction, such as Tax Calculation or Invoice calculation.
Integration Tests: these are the most common because they test APIs (and DBs too) to ensure that different parts of the system work correctly, for example examining the prices and coupon rates and ensuring everything is logged correctly in the database.
-
E2E (End-To-End) Tests: these test specific Use Cases from A to Z, like an actual User, because it tests the entire User Story, it can involve things such as automated UI Interactions, API Calls, DB queries etc... to ensure that everything is working as expected
Test Lifecycle Phases
in general there's 3 phases to any test iteration:
1. Setup:
This involves Setting up a test Database and populating it with mock data and other things related to this.
2. Assertions:
Acts are the functions that need to be executed using test/mock inputs.
Assertion(s) are the checks that wrap those Acts, usually we would have mapped out a logical sequence of how our system will behave, and we will be able to predict specific values if specific inputs were given, therefor we test the piece of code against our expected result:
- if it's the same/approximate, then it works as it should
- if not then that piece of code is faulty and should be revised.
3. Teardown:
After running all of our "Test Suites" we then have to drop the test database in order to make our development environment clean for future test iterations.
Test Suites
In order to make our tests organized, we put them in something called "Test Suites".
For example in an e-commerce website there are a lot of specific use cases that are similar, such as "Authentication", "Invoicing", "Ordering" etc...
in the picture above we have a Test Suite for "User Authentication", it could have individual Integration tests such as:
- "Log using right credentials"
- "Fail Log using wrong credentials"
- "Create new User on data that already exists"
- "Try to perform an SQL injection on the Auth API"
- etc...
Code Snippets: NodeJS with Jest
The Setup
// setup.js
module.exports = async () => {
console.log("\nGlobal Setup: Starting Test Environment...");
await db_client.executeRaw`DROP DATABASE IF EXISTS test_db`;
await db_client.executeRaw`CREATE DATABASE test_db`;
// Create the Relational model
execSync('npx db_client deploy');
// Populate the database with test data (aka "Seeding")
execSync('npx db_client seed');
console.log("✅ Global Setup Complete\n");
};
A Unit Test
// tests/unit/tvaCalculation.js
const { calculateTVA, calculateTotalWithTVA } = require('specific_folder/utils/calculation/tva.js');
/** In Jest, we define a Test suite with "describe"
* 1st parameter: the description of that test suite
* 2nd parameter: a callback function which contains test functions
*/
describe('TVA Calculation Tests', () => {
/** In Jest, we define a single test with "it" or "test"
* 1st parameter: the description of that individual test
* 2nd parameter: a callback function which contains the actual test code
*/
it('should throw an error for negative amounts', () => {
const amount = -100;
const result = calculateTVA(amount);
expect(result).toThrow('Amount cannot be negative');
});
it('should calculate TVA for zero amount correctly', () => {
const amount = 0;
const expectedTVA = 0; // 20% of 0 is 0
expect(calculateTVA(amount)).toBe(expectedTVA);
});
});
An Integration Test
// tests/integration/user/register.js
const request = require("supertest"); // used to interact with the backend and make API calls
const server_app = require("app");
let app;
beforeAll(() => {
// This assigns the server to an object that will be used by these tests (regarding the API atleast)
app = request(server_app);
});
describe("User Registration Tests", () => {
it("Should create an account with valid details", async () => {
const res = await app.post("/signup").send({
f_name: "John",
l_name: "Doe",
g: "M",
phoneN: "+4436256526",
email: 'testUser@gmail.com',
p: `${hash(12345678)}`
});
// an Account has been created sucessfully
expect(res.status).toBe(200);
expect(res.body).toBeDefined();
const resDb = await db_client.getTable("Utilisateur").getRow({
where: {
email: "testUser@gmail.com"
}
});
expect(resDb).toBeDefined();
// an Email Verification Token has been generated
expect(resDb.email_verification_token).toBeTruthy();
});
});
An E2E Test
// tests/e2e/order/checkoutFlow.js
describe('E-commerce Checkout Flow', () => {
it('should search, add to cart, and checkout', () => {
// Visit the homepage
cy.visit('https://example-ecommerce.com');
// Search for a product
cy.get('input[placeholder="Search products"]').type('Laptop');
cy.get('button[type="submit"]').click();
// Verify search results
cy.contains('Laptop').should('be.visible');
// Add first product to cart
cy.get('.product-item').first().find('button.add-to-cart').click();
// Verify the cart has 1 item
cy.get('.cart-icon').click();
cy.get('.cart-items').should('have.length', 1);
// Proceed to checkout
cy.get('.checkout-button').click();
// Verify checkout page contains the product
cy.contains('Laptop').should('be.visible');
const resDb = await db_client.getTable("ShoppingCart").getLatestRow();
expect(resDb.orderItems).toBeDefined();
expect(resDb.orderItems[0].name).toBe("Laptop A1");
});
});
The Teardown
// Teardown.js
module.exports = async () => {
console.log("\n Global Teardown: Cleaning Up Test Environment...");
await db_client.executeRaw`DROP DATABASE IF EXISTS test_db`;
await db_client.disconnect();
console.log("✅ Global Teardown Complete\n");
};




Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.