JavaScript > Testing and Debugging > Unit Testing > Writing test cases

Simple Calculator Unit Tests with Jest

This example demonstrates how to write unit tests for a simple calculator class using Jest, a popular JavaScript testing framework. It includes test cases for addition, subtraction, multiplication, and division, covering both positive and negative numbers, as well as edge cases like division by zero.

The Calculator Class

This code defines a Calculator class with four basic arithmetic operations: add, subtract, multiply, and divide. The divide method includes error handling for division by zero.

class Calculator {
  add(a, b) {
    return a + b;
  }

  subtract(a, b) {
    return a - b;
  }

  multiply(a, b) {
    return a * b;
  }

  divide(a, b) {
    if (b === 0) {
      throw new Error('Division by zero is not allowed.');
    }
    return a / b;
  }
}

Jest Test Suite

This is the Jest test suite for the Calculator class. It uses describe blocks to group tests for each operation (addition, subtraction, multiplication, and division). beforeEach is used to create a new Calculator instance before each test, ensuring each test runs in isolation. Each it block represents a single test case with a specific assertion using expect. The toThrow matcher is used to verify that the divide method throws an error when dividing by zero.

describe('Calculator', () => {
  let calculator;

  beforeEach(() => {
    calculator = new Calculator();
  });

  describe('Addition', () => {
    it('should add two positive numbers', () => {
      expect(calculator.add(2, 3)).toBe(5);
    });

    it('should add a positive and a negative number', () => {
      expect(calculator.add(2, -3)).toBe(-1);
    });
  });

  describe('Subtraction', () => {
    it('should subtract two positive numbers', () => {
      expect(calculator.subtract(5, 2)).toBe(3);
    });

    it('should subtract a negative number from a positive number', () => {
      expect(calculator.subtract(5, -2)).toBe(7);
    });
  });

  describe('Multiplication', () => {
    it('should multiply two positive numbers', () => {
      expect(calculator.multiply(2, 3)).toBe(6);
    });

    it('should multiply a positive and a negative number', () => {
      expect(calculator.multiply(2, -3)).toBe(-6);
    });
  });

  describe('Division', () => {
    it('should divide two positive numbers', () => {
      expect(calculator.divide(6, 2)).toBe(3);
    });

    it('should handle division by zero', () => {
      expect(() => calculator.divide(6, 0)).toThrow('Division by zero is not allowed.');
    });
  });
});

Concepts Behind the Snippet

This snippet demonstrates the fundamental concepts of unit testing: isolating units of code (in this case, the methods of the Calculator class) and verifying their behavior in different scenarios. It uses Jest, a popular testing framework, to write assertions and organize the tests. The goal is to ensure that each function behaves as expected and that any errors are caught early in the development process.

Real-Life Use Case Section

Unit testing is crucial in any software development project, especially when dealing with complex logic or critical calculations. Imagine building a financial application; you want to ensure that all calculations, like interest rates and loan amounts, are accurate. Writing unit tests for each function allows you to verify the correctness of the code and prevent costly errors in production.

Best Practices

  • Test-Driven Development (TDD): Write the tests before writing the code. This helps you think about the desired behavior of the code before you implement it.
  • Keep Tests Isolated: Each test should be independent of other tests. Use mocking or stubbing to isolate dependencies.
  • Write Clear and Concise Tests: Tests should be easy to understand and maintain. Use descriptive names for test cases and assertions.
  • Cover Edge Cases: Test for boundary conditions and unusual inputs to ensure that the code handles them correctly.

Interview Tip

When discussing unit testing in an interview, emphasize the importance of writing tests early and often. Explain how unit tests help you catch bugs early, improve code quality, and make refactoring easier. Be prepared to discuss different testing frameworks and their pros and cons. Demonstrate your understanding of TDD principles.

When to Use Them

Use unit tests whenever you write new code, especially functions or methods that perform specific tasks. They are particularly valuable for code that is complex, critical, or likely to change over time. Also, run unit tests whenever you refactor existing code to ensure that you haven't introduced any regressions.

Memory Footprint

Unit tests generally have a minimal memory footprint during runtime. The testing framework itself may consume some memory, but the tests are designed to run quickly and efficiently. Focus on writing efficient code in the application itself to optimize memory usage.

Alternatives

Alternative testing frameworks to Jest include Mocha, Jasmine, and Ava. Integration tests are used to test the interactions between different parts of the system. End-to-end tests simulate real user interactions with the application.

Pros

  • Early Bug Detection: Unit tests help identify bugs early in the development cycle, reducing the cost of fixing them later.
  • Improved Code Quality: Writing tests encourages you to write cleaner and more modular code.
  • Easier Refactoring: Unit tests provide a safety net when refactoring code, allowing you to make changes with confidence.
  • Documentation: Tests can serve as documentation for the code, showing how it is intended to be used.

Cons

  • Time Investment: Writing unit tests takes time and effort.
  • Maintenance Overhead: Tests need to be maintained and updated as the code changes.
  • False Sense of Security: Unit tests only cover the specific scenarios they are designed for. They may not catch all possible bugs.

FAQ

  • What is the purpose of the beforeEach block?

    The beforeEach block is used to set up the environment for each test. In this example, it creates a new instance of the Calculator class before each test case, ensuring that each test runs in isolation.
  • What is the difference between toBe and toEqual in Jest?

    toBe is used for strict equality comparisons (===), while toEqual is used for deep equality comparisons, meaning it compares the values of the properties of objects or arrays.
  • How do I run these tests?

    First, make sure you have Node.js and npm installed. Install Jest using npm install --save-dev jest. Then, add a test script to your package.json file: "test": "jest". Finally, run the tests using npm test.