Front End

Building Javascript Test Framework From Scratch

Hey, Javascript enthusiast! Let’s do something fun today, building a Javascript test framework from scratch. Building your own JavaScript test framework is a great way to understand the inner workings of testing frameworks/libraries like Jest or Mocha. In this tutorial, we’ll create a simple yet powerful test framework from scratch, without relying on any external libraries, complete with helper functions like expect, describe, and test. By the end, you’ll have a fully functional testing environment that runs directly in your browser, with clear pass/fail visuals for each test case. All with plain vanilla Javascript, CSS and HTML.

What’s in this Minimalistic Javascript Test Framework

  • Simple test helpers (expect, describe, test)
  • Matchers like toBe, toContain, toMatch
  • Easy execution within an HTML page and visual representation of test results
  • No dependencies or complex setups are required.

Step 1: Define the Core Helper Functions

1.1 The expect Function

The expect function is the backbone of our test framework. It allows us to make assertions about values and provides methods like toBe, toContain, and toMatch.

function expect(actual) {
    return {
        toBe(expected) {
            if (actual !== expected) {
                throw new Error(`Expected ${expected}, but received ${actual}`);
            }
        },
        toContain(item) {
            if (!actual.includes(item)) {
                throw new Error(`Expected array to contain ${item}, but it did not.`);
            }
        },
        toMatch(regex) {
            if (!regex.test(actual)) {
                throw new Error(`Expected string to match ${regex}, but it did not.`);
            }
        }
    };
}Code language: JavaScript (javascript)

1.2 The test Function

The test function defines individual test cases. Each test will execute a callback function, and we’ll track whether it passes or fails.

const tests = [];

function test(description, callback) {
    tests.push({ description, callback });
}Code language: JavaScript (javascript)

1.3 The describe Function

The describe function groups related tests together. While optional, it improves readability and organization.

function describe(suiteName, callback) {
    console.group(suiteName);
    callback();
    console.groupEnd();
}Code language: JavaScript (javascript)

Let’s add all the above scripts in a file named `testFramework.js`.

Step 2: Running Tests on an HTML Page

To run our tests, we’ll create a simple HTML page that displays the results visually.

2.1 HTML Structure

Create an index.html file with the following structure:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Minimalistic Test Framework</title>
    <style>
        .test-result { margin: 10px; padding: 10px; border-radius: 5px; }
        .pass { background-color: #d4edda; color: #155724; }
        .fail { background-color: #f8d7da; color: #721c24; }
    </style>
</head>
<body>
    <h1>Test Results</h1>
    <div id="results"></div>
    <script src="testFramework.js"></script>
    <script src="tests.js"></script>
</body>
</html>Code language: HTML, XML (xml)

Note: make sure the charset is defined properly; otherwise results might not render correctly (due to emoji etc.)

2.2 Running Tests Dynamically

Let’s add the following script to our testFramework.js to execute the tests and display their results:

document.addEventListener('DOMContentLoaded', () => {
    const resultsDiv = document.getElementById('results');
    let passed = 0;
    let failed = 0;

    tests.forEach(({ description, callback }) => {
        try {
            callback();
            const resultDiv = document.createElement('div');
            resultDiv.className = 'test-result pass';
            resultDiv.textContent = `✅ PASS: ${description}`;
            resultsDiv.appendChild(resultDiv);
            passed++;
        } catch (error) {
            const resultDiv = document.createElement('div');
            resultDiv.className = 'test-result fail';
            resultDiv.textContent = `❌ FAIL: ${description} - ${error.message}`;
            resultsDiv.appendChild(resultDiv);
            failed++;
        }
    });

    // Display summary
    const summaryDiv = document.createElement('div');
    summaryDiv.textContent = `Summary: Passed: ${passed}, Failed: ${failed}`;
    resultsDiv.appendChild(summaryDiv);
});Code language: JavaScript (javascript)

Step 3: Writing Test Cases

Now that our framework is ready, it is time to write some test cases!

Example Test Cases:

describe('String Operations', () => {
    test('should concatenate strings', () => {
        const result = 'Hello, ' + 'World!';
        expect(result).toBe('Hello, World!');
    });

    test('should include substring', () => {
        const str = 'Hello, World!';
        expect(str).toContain('World');
    });
});

describe('Array Operations', () => {
    test('should find item in array', () => {
        const arr = [1, 2, 3, 4];
        expect(arr).toContain(3);
    });

    test('should match regex', () => {
        const email = 'test@example.com';
        expect(email).toMatch(/^[^@]+@[^@]+\.[^@]+$/);
    });
});Code language: PHP (php)

Put these test cases in a separate file named tests.js

Step 4: Visualizing Test Results

When you open the index.html file in your browser, you’ll see a clear visual representation of your test results. Passed tests are highlighted in green, while failed tests are marked in red with error messages.

And it’s done. All test results are running and showing the results in a nice visual without depending on any explicit libraries/dependencies! 🎉

Conclusion: A Lightweight Testing Solution

By building this minimalistic JavaScript test framework, hopefully, you’ve gained some insight into how testing tools operate under the hood and had some fun along the way. This framework is easy to set up, doesn’t rely on any external libraries, and provides clear pass/fail visuals for your test cases. However, I wouldn’t recommend this in production settings. Maybe in a special scenario where you are really trying to avoid third-party dependency/build pipeline but still want to ensure high-quality code with test coverage.

Rana Ahsan

Rana Ahsan is a seasoned software engineer and technology leader specialized in distributed systems and software architecture. With a Master’s in Software Engineering from Concordia University, his experience spans leading scalable architecture at Coursera and TopHat, contributing to open-source projects. This blog, CodeSamplez.com, showcases his passion for sharing practical insights on programming and distributed systems concepts and help educate others. Github | X | LinkedIn

Recent Posts

Service Worker Best Practices: Security & Debugging Guide

You've conquered the service worker lifecycle, mastered caching strategies, and explored advanced features. Now it's time to lock down your implementation with battle-tested service worker…

1 week ago

Advanced Service Worker Features: Push Beyond the Basics

Unlock the full potential of service workers with advanced features like push notifications, background sync, and performance optimization techniques that transform your web app into…

3 weeks ago

Service Workers in React: Framework Integration Guide

Learn how to integrate service workers in React, Next.js, Vue, and Angular with practical code examples and production-ready implementations for modern web applications.

1 month ago

This website uses cookies.