Skip to content

Instantly share code, notes, and snippets.

@halitbatur
Created July 23, 2024 07:14
Show Gist options
  • Save halitbatur/087e799d5f3f7137dc159896e1421c24 to your computer and use it in GitHub Desktop.
Save halitbatur/087e799d5f3f7137dc159896e1421c24 to your computer and use it in GitHub Desktop.
Jest and Supertest

Testing Discussion #2

  1. What is the role of the describe and it functions in Jest? How do they help in organizing and structuring test cases?
  2. Explain how to use Supertest for testing HTTP endpoints in an Express.js application. What are the steps involved in setting up Supertest?
  3. Discuss the purpose of mocking in Jest. How can you use jest.mock to simulate dependencies in your tests? Provide an example of mocking a database module in an Express.js application.
  4. How can you use Jest’s expect function to perform assertions in your tests? Provide examples of different types of assertions (e.g., toEqual, toBe, toMatchObject) and explain when to use each.
  5. What are hooks in Jest, and how can they be used to set up and tear down test environments? Describe the differences between the hooks and provide scenarios where each hook might be appropriately used.
@MissAngelaKing
Copy link

@Letagoeve, @MissAngelaKing, @Vuyo-Ngwane

  1. Describe groups related test cases together. It acts like a heading for a section of tests focusing on a specific functionality or unit.
    it (or test) defines an individual test case within a describe block. It describes a specific scenario you want to test with a clear and readable sentence.
    By using these functions, you organize your tests logically. It's like creating chapters and subheadings in a book to make the test suite easy to navigate and understand.

  2. To use Supertest for testing HTTP endpoints in an 2.Express.js application, start by installing Supertest via npm. Then, import Supertest and Express app in test file, creating a test instance of the app with const testApp = request(app). Use this instance to send HTTP requests to your app, such as testApp.get('/endpoint').expect(200), and add assertions to verify the response using expect(res.body).toEqual({ message: 'Hello' }). Finally, run tests using a testing framework like Jest or Mocha. This allows you to easily test your app's endpoints and ensure they return the expected responses, making it a valuable tool for ensuring the reliability and stability of your Express.js application.

  3. Mocking in Jest is used to simulate dependencies in tests, allowing for isolation of the component being tested and control over dependency behavior. jest.mock is used to replace actual dependencies with mock implementations, enabling testing of modules reliant on external dependencies like databases or APIs.
    For example, in an Express.js app, you can mock a db.js module's getUser function using jest.mock('./db') and configure it to return a specific value using db.getUser.mockResolvedValue. This allows testing of the GET /user endpoint without actual database queries, making tests faster and more reliable. By mocking dependencies, you can focus on testing the specific component's logic, ensuring accurate results and efficient testing.

  4. Jest's expect function is used for making assertions in your test cases. It allows you to assert that certain conditions are met and provides various matcher functions to perform these assertions. Here are examples of different types of assertions and explanations of when to use each:

  5. toEqual
    The toEqual matcher checks for deep equality between the actual and expected values. It is useful when you want to compare objects or arrays.
    test('adds 1 + 2 to equal 3', () => {
    expect(1 + 2).toEqual(3);
    });

test('checks object equality', () => {
const data = { name: 'John', age: 30 };
expect(data).toEqual({ name: 'John', age: 30 });
});
2. toBe
The toBe matcher compares for strict equality (===), which means it checks if the actual and expected values are the same object in memory or are primitives with the same value.
Example:
javascript
Copy code
test('adds 1 + 2 to be 3', () => {
expect(1 + 2).toBe(3);
});
test('checks strict equality', () => {
const data = { name: 'John', age: 30 };
expect(data).not.toBe({ name: 'John', age: 30 }); // Different objects in memory
});
3. toMatchObject
The toMatchObject matcher checks if the received object contains properties that match the expected object. It is useful when you want to verify that an object has certain key-value pairs without necessarily checking all properties.
Example:
javascript
Copy code
test('checks object properties using toMatchObject', () => {
const data = { name: 'John', age: 30, city: 'New York' };
expect(data).toMatchObject({ name: 'John', age: 30 });
});
});
4. toBeDefined / toBeUndefined
The toBeDefined and toBeUndefined matchers are used to assert whether a variable or property is defined or undefined, respectively.
Example:
javascript
Copy code
test('checks if variable is defined', () => {
const value = 10;
expect(value).toBeDefined();
});
test('checks if variable is undefined', () => {
const value = undefined;
expect(value).toBeUndefined();
});
5. toHaveLength
The toHaveLength matcher is used to assert the length of arrays or strings.
Example:
javascript
Copy code
test('checks array length', () => {
const array = [1, 2, 3];
expect(array).toHaveLength(3);
});
test('checks string length', () => {
const str = 'hello';
expect(str).toHaveLength(5);
});
6. toThrow
The toThrow matcher checks if a function throws an error when called.
Example:
javascript
Copy code
function throwError() {
throw new Error('something went wrong');
}
test('throws an error', () => {
expect(throwError).toThrow();
expect(throwError).toThrowError('something went wrong');
});

When to Use Each Matcher:
toEqual: Use when you want to compare complex objects or arrays for deep equality.
toBe: Use when you want to compare primitive values or check strict object equality.
toMatchObject: Use when you want to check if an object contains certain properties or key-value pairs.
toBeDefined / toBeUndefined: Use when you want to verify if a variable or property is defined or undefined.
toHaveLength: Use when you want to check the length of arrays or strings.
toThrow: Use when you want to check if a function throws an error.

  1. React Hooks provide a way to create components with features like state, without the need for class components. They allow you to connect to React state and lifecycle features directly from functional components. Here are the key hooks and their use cases:

beforeAll and afterAll:
Purpose: These top-level hooks apply to every test in a file. They allow you to set up and tear down shared resources or state before and after all tests.
Use Cases:
Initializing a database connection.
Setting up a mock server.
Creating a shared context for multiple tests.
beforeEach and afterEach:
Purpose: These hooks apply only to the tests within a specific describe block. They allow you to set up and clean up resources before and after each test.
Use Cases:
Resetting state or variables.
Mocking API calls.
Isolating test-specific setup.

@Hophneylen
Copy link

@Hophneylen
@KhileM
Koketso Lepulana

In Jest, the describe function is used to group related test cases together, creating a test suite that enhances readability and organization. Within a describe block, the it function is used to define individual test cases.
This structure helps in clearly delineating the scope of each test suite and its respective test cases, making it easier to understand, maintain, and debug the code.
2.
Supertest is a powerful tool for testing HTTP endpoints in Node.js applications, especially those built with Express.js.
step-by-step guide on how to use Supertest for testing my Express.js application:
Step 1: Install Dependencies
Step 2: Set Up Jest (using Jest)
Step 3: Create Your Express App
Step 4: Write Tests Using Supertest
Step 5: Run the Tests

3.Mocking in Jest is like giving your tests a way to pretend certain parts of your code work in a specific way. It’s super useful when you want to focus on testing just one part of your application without getting bogged down by the complexities of the other parts.
Mocking the Module: When you use jest.mock('./db', () => ({ getUsers: jest.fn() })), you're telling Jest to replace the real db module with a fake version. This fake version has a getUsers function that’s a Jest mock function.
Controlling the mock: You can control what the mock function returns. For example, db.getUsers.mockResolvedValue(mockUsers) makes it return a resolved promise with mockUsers, as if the database successfully returned some users. Similarly, db.getUsers.mockRejectedValue(new Error('Database Error')) makes it return a rejected promise, simulating a database error.
writing tests: Using supertest, you can make HTTP requests to your Express app and check the responses. You’re essentially pretending to be a client hitting your API endpoints and verifying that your app behaves as expected.
4.
Using Jest’s expect function, can help write assertions to check if the code works as expected.

  • toBe
    Use toBe for exact equality. It’s great for comparing primitive values or checking if two references point to the same object.
  • toEqual
    Use toEqual for deep equality checks. It compares the values inside objects or arrays.
  • toMatchObject
    Use toMatchObject to check if an object matches part of another object. This is useful when you don’t need to compare every property.
  • toContain
    Use toContain to check if an item is in an array or if a substring is in a string

Hooks in Jest are functions like beforeAll, beforeEach, afterAll, and afterEach that help set up and tear down test environments. beforeAll runs once before all tests in a suite, useful for one-time setup like database connections. beforeEach runs before each test, ideal for resetting state. afterAll runs once after all tests, used for cleanup like closing connections. afterEach runs after each test, suitable for cleaning up resources. These hooks ensure tests are isolated and environments are correctly managed.

@Yenkosii
Copy link

Yenkosii commented Jul 23, 2024

@thabiso-makolana @mpilomthiyane97 @Yenkosii

  1. Defines a test suite or group of related tests and provide a clear and hierarchical way to group related tests, making your test suite more readable and maintainable.

  2. Supertest is a high-level abstraction for testing HTTP servers. It provides a simple API for making HTTP requests and inspecting responses. When combined with Jest, it's a powerful tool for testing Express.js applications. Setting Up Supertest

Install dependencies:

(a)Install dependencies:npm install --save-dev supertest jest

(b)Create a test file: Create a file named your-test-file.test.js (or any name ending in .test.js) in your project's test directory.

(c)Basic Structure of a Test:

const request = require('supertest');

const app = require('../app'); // Replace with your app file

describe('Your API', () => {

it('should respond with 200 for GET /', async () => {

const response = await request(app).get('/');

expect(response.status).toBe(200);

});

});

  1. Mocks allow developers to isolate the code being tested by replacing real dependencies with controlled substitutes. This enables testing of individual units of code without relying on the behavior of external components.

The jest.mock function is a powerful tool for isolating and testing code that relies on external dependencies. Here's how you can use it to simulate dependencies in your tests:

a. Mocking Entire Modules:

Use jest.mock('module_name') at the beginning of your test file. This replaces the actual implementation of the module with a mock object during the test.

You can then interact with the mocked module's exports like normal functions. Jest provides methods to configure these mocks, such as:
Setting return values for function calls
Verifying how many times a function was called
Checking the arguments passed to function calls

b. Manual Mocks vs Automatic Mocks:
Jest offers two approaches for mocking dependencies:
Manual Mocks: You explicitly define a mock object with the desired behavior using Jest's mocking functionalities. This gives you full control over the mock's behavior.
Automatic Mocks (Default): Jest automatically mocks any module imported by your test file. This is convenient but might require disabling it for specific dependencies.
c. Example: Mocked API Call
Imagine you're testing a function that fetches data from an API. Here's how you could mock the API call using jest.mock:

JavaScript
// api.js (original module)
export function fetchData() {
// Makes actual API call
}

// yourFile.test.js
jest.mock('./api'); // Mock the api module

import { fetchData } from './yourFile';
import api from './api'; // Now api is a mock object

test('fetches data from API', async () => {
const mockData = { name: 'Mocked Data' };
api.fetchData.mockResolvedValue(mockData); // Set mock return value

const data = await fetchData();
expect(data).toEqual(mockData);

// Assert that api.fetchData was called
expect(api.fetchData).toHaveBeenCalledTimes(1);
});

By mocking the fetchData function, you can test your code's logic without relying on actual API calls. This improves test isolation and makes them faster and more reliable.

An example of mocking a database module (assuming a simple db.js module) in an Express.js application using Jest:
(db.js)
// Simulates a basic database interaction
export function getUser(id) {
// ... (implementation to access database)
}

(UserController.test.js)
const UserController = require('./userController'); // Your controller
jest.mock('./db'); // Mock the db module

test('gets user by ID', async () => {
const mockUser = { id: 1, name: 'John Doe' };
db.getUser.mockResolvedValueOnce(mockUser); // Mock return value once

const req = { params: { id: 1 } }; // Simulate request object
const res = { json: jest.fn() }; // Mock response object

await UserController.getUserById(req, res);

expect(res.json).toHaveBeenCalledWith(mockUser); // Assert response
expect(db.getUser).toHaveBeenCalledWith(1); // Assert db call
});

  1. Inside the test suite you call your expect function.

toBe():Use for exact equality comparisons of primitive values.


test('primitive equality', () => {

  expect(2 + 2).toBe(4);

  expect('hello').toBe('hello');

  expect(true).toBe(true);

});

**toEqual():

Use for deep equality comparisons of objects or arrays.**


test('object equality', () => {

  const obj = { a: 1, b: 2 };

  expect(obj).toEqual({ a: 1, b: 2 });

});


test('array equality', () => {

  const arr = [1, 2, 3];

  expect(arr).toEqual([1, 2, 3]);

});

**toMatchObject():

Use when you want to check if an object contains a subset of properties.**


test('object matching', () => {

  const user = { id: 1, name: 'John', age: 30 };

  expect(user).toMatchObject({ name: 'John', age: 30 });

});

Hooks in Jest are special functions that allow you to run code at specific points during the testing lifecycle.

beforeAll - Runs once before all tests in a describe block.Useful for setup operations that only need to happen once, like database connections or loading shared resources.

afterAll - Runs once after all tests in a describe block have completed.Useful for cleanup operations that only need to happen once, like closing database connections or cleaning up shared resources.

beforeEach - Runs before each test in a describe block.Useful for setting up a fresh state for each test, like resetting a mock or initializing a new instance of an object.

afterEach - Runs after each test in a describe block.Useful for cleaning up after each test, like clearing mocks or resetting modified global state.

@NonhlanhlaMazibuko
Copy link

NonhlanhlaMazibuko commented Jul 23, 2024

Sharon Matjila
Thitevhelwi Masuvhe
Nonhlanhla Mazibuko

  1. Grouping: The main function of describe is to put similar test cases in one group. This facilitates the logical organisation of your test suite and clarifies the connections between various tests.
  • Nesting: To better categorise your tests, you can nest describe blocks within one another to form a hierarchical structure. This is very helpful for intricate test suites.
  • Test Suite Metadata: A string that serves as the group of tests' description or title is the first argument to be described. This description is useful for debugging and can be found in the test output.

It:

  • Individual Tests: Within a describe block, the it function defines a single test case. Every it block stands for a certain situation or state that you wish to examine.
  • Test Case Metadata: It takes a string as its first input, which explains the particular test case. The test output also includes this description.
    Expectations: To make statements about the behavior of your code, you usually utilise assertion functions like expect inside of an it block. Jest determines whether the test passes or fails by comparing these expectations with the actual outcomes.
  1. Supertest is a powerful library for testing HTTP endpoints in Node.js applications. It works seamlessly with Express.js to help developers write integration tests for their routes. Here are the steps to set up and use Supertest for testing HTTP endpoints in an Express.js application:

-Install required packages like mocha or jest
eg. npm install supertest --save-dev
npm install mocha --save-dev # or jest, if you prefer it

  • Set up your express application
  • Create a test file e.g app.test.js
  • Configure your test script in your package.json file
  • Run your tests using npm test
  1. Isolation: Mocking allows you to isolate the unit of code you're testing from its dependencies. This prevents external factors from influencing your test results, ensuring that you're testing the specific logic of your function or component.
    Speed: Real dependencies (like databases, network requests, or file systems) can be slow and resource-intensive. Mocking these interactions makes your tests significantly faster, improving your development workflow.
    Determinism: Mocking lets you control the behavior of dependencies, setting up specific scenarios or edge cases that might be difficult to reproduce with real dependencies. This enhances the reliability and repeatability of your tests.
    Simplifying Setup: Mocking eliminates the need for complex setup or test data required by real dependencies. This makes your tests cleaner, more focused, and easier to write.

Using jest.mock
Jest's jest.mock function is your go-to tool for simulating dependencies. It replaces the actual module with a mock version that you can configure.
JavaScript
jest.mock('./moduleName', () => ({
// Mock implementations of functions/values from the module
}));

Mocking a Database Module in Express.js (Example)
Let's say you have an Express.js route that interacts with a database to fetch user data:
JavaScript
// users.js
const db = require('./db'); // Your database module

async function getUser(req, res) {
const user = await db.getUserById(req.params.userId);
res.json(user);
}

example of mocking a database module in an Express.js application:

  • We mock the db module and its getUserById function.
  • We configure getUserById to return a mock user object.
  • We use supertest to simulate an HTTP request to our Express app.
  • We assert that the response contains the expected data (the mocked user object). JavaScript
    // users.test.js
    jest.mock('./db', () => ({
    getUserById: jest.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
    }));

const request = require('supertest');
const app = require('./app'); // Your Express app

describe('GET /users/:userId', () => {
it('should return user details', async () => {
const response = await request(app).get('/users/1');

expect(response.status).toBe(200);
expect(response.body).toEqual({ id: 1, name: 'Alice' });

});
});

  1. Jest's expect function is used to create assertions in your tests, allowing you to verify that your code behaves as expected. Here are some examples of different types of assertions you can use with Jest, along with explanations of when to use each:
  • toBe
    Use toBe for exact equality. This is similar to === in JavaScript.
    example
    test('two plus two is four', () => {
    expect(2 + 2).toBe(4);
    });

  • toEqual
    Use toEqual to check the value of an object or array. This is a deep equality check, meaning it recursively checks the equality of all properties.
    example
    test('object assignment', () => {
    const data = { one: 1 };
    data['two'] = 2;
    expect(data).toEqual({ one: 1, two: 2 });
    });

  • toMatchObject
    Use toMatchObject to check that an object matches a subset of properties.
    example
    test('object contains properties', () => {
    const data = { one: 1, two: 2, three: 3 };
    expect(data).toMatchObject({ one: 1, two: 2 });
    });

  1. Hooks in Jest are special functions also called setup and teardown functions which help prepare the test environment before tests run and clean it up afterward and allows to run code at specific points in the test lifecycle. These functions are useful for managing shared resources, initializing variables, or any other setup required to ensure tests run in a clean state. These are the hooks:
  • beforeAll(): This function runs once before all the tests in a file. It is typically used for expensive setup tasks that only need to be done once.

  • beforeEach(): This function runs before each test in a file. It is useful for setting up state or variables that need to be reset before each test.

  • afterAll(): This function runs once after all the tests in a file have completed. It is used to clean up any resources that were set up in beforeAll().

  • afterEach(): This function runs after each test in a file. It is used to reset or clean up the state after each test.

@NtokozoMitchell
Copy link

NtokozoMitchell commented Jul 23, 2024

@Geraldmutsw
@Mukoni-Nemauluma

1.In Jest, describe and it work together to organize and structure your test cases, making them more readable and maintainable. Here's how: Describe, groups related test cases under a descriptive name, often used to represent a specific feature, component, or function in your code. It defines an individual test case within a describe block.

  1. Supertest is a powerful tool for testing HTTP endpoints and the steps involved in setting it up start from installing the Supertest module as a development dependency using NPM. Then in the test file import Supertest and the express app. We will also use a testing framework like Jest because Jest provides a describe and it structure to organize the tests. In the test file we will use a Supertest agent representing the express app, then chain methods to build HTTP requests and lastly use Jest's assertions to verify the response.

  2. Mocking in Jest involves creating simulated versions of dependencies to test code more effectively. This technique allows you to control the behavior of these mock dependencies, avoiding unpredictable issues from real components. By isolating the code under test from its dependencies, mocking improves test reliability and speed. For instance, in an Express.js app, you can use jest.mock to create a mock database module that mimics various scenarios, such as successful data retrieval or errors. This enables thorough testing of route handlers without relying on a real database, ensuring tests focus solely on application logic.

In an Express.js application managing user registration with MongoDB, testing the POST /register route without actual database calls is essential for efficient development. Jest's mocking capabilities allow you to replace the database.js module with a mock version in your tests. This mock setup enables simulation of database operations like user insertion and error handling, ensuring thorough testing of the route's behavior in various scenarios. By using mocks, you create a controlled testing environment where you can validate the registration logic independently of MongoDB's actual state or connectivity,

  1. (i) toEqual: test('toEqual example', () => {
    const data = { id: 1, name: 'John Doe' };
    expect(data).toEqual({ id: 1, name: 'John Doe' });
    });
    (ii) toBe: test('toBe example', () => {
    const value = 10;
    expect(value).toBe(10);
    });
    (ii) toMatchObject: test('toMatchObject example', () => {
    const data = { id: 1, name: 'John Doe', age: 30 };
    expect(data).toMatchObject({ name: 'John Doe', age: 30 });
    });

when to use:
toEqual: Use toEqual to check if two objects or arrays have the same properties with the same values.
toBe: Use toBe to compare simple values like numbers or strings to see if they are exactly the same.
toMatchObject: Use toMatchObject to check if an object has specific properties and values, without needing to check every single detail.

5.Hooks are functions that allows us to perform functions before and after tests.
(a)beforeEach and afterEach Hooks:
These hooks run before and after each test case, respectively.
Use beforeEach to set up any common state or perform actions needed for multiple tests.
Use beforeAll to establish a database connection or start a server once for all tests in a suite, improving efficiency.

(b)beforeAll and afterAll Hooks:
These hooks run once before and after all test cases in a file.
Use beforeAll for one-time setup, especially when it’s asynchronous and can’t be done inline.
Teardown: Use afterEach to clean up temporary files or reset state after each test, preventing side effects between tests.

@ImisebenziEmihle
Copy link

ImisebenziEmihle commented Jul 23, 2024

@bonolo @Lethukuthula @ImisebenziEmihle

1** Role of Describe()** :

Grouping Related Tests:
describe acts as a container for all tests related to a specific functionality, module, or class.
This grouping allows you to categorize tests logically, making them easier to understand and navigate.
It enhances the readability of your test suite, especially as it grows in size.

Nesting for Deeper Hierarchy:
You can further organize tests by nesting describe blocks within each other.
This creates a hierarchical structure that reflects the relationships between components or functionalities being tested.

How they help in organizing & structuring test cases :
Descriptive Naming:
When using describe, we provide clear and concise names that accurately describe the tests being grouped.
This name becomes the heading for the section and helps developers quickly identify the focus of the tests.
Improved Readability: A well-structured test suite with clear groupings is easier to comprehend for both beginners and experienced developers.
Maintainability: As your codebase and test suite evolve, adding or modifying tests becomes simpler with clear boundaries established by describe blocks.
Focus on Specific Areas: When debugging or investigating an issue, developers can easily locate relevant tests by filtering according to the describe sections.
Modular Testing: Breaking down the test suite into smaller, well-defined units with describe facilitates parallel test execution (if your testing framework supports it), potentially reducing test run times.
Role of it() :
Defining Individual Tests:
Nestled within describe blocks, it() functions define individual test cases. Each it() specifies a specific behavior or functionality being tested.
Descriptive Names:
As with describe, we provide meaningful names for it() functions that capture the essence of the test being performed.
This clarity allows developers to easily grasp the expected behavior without needing to delve into the implementation details.

  1. Supertest is a Node.js library used for testing HTTP endpoints in an Express.js application. To use Supertest, first, install it using npm install supertest. Import Supertest and your Express app in your test file. Use Supertest's request function to make HTTP requests to your endpoints. You can chain methods like .get(), .post(), .put(), or .delete() to specify the HTTP method. Chain .expect() to set the expected HTTP status code and response. Finally, call .end(done) to finish the test. This allows you to automate and validate the behavior of your HTTP endpoints effectively.
    2.1 To set up Supertest, follow these steps:
    Install Supertest: Run npm install supertest.
    Create Test File: Create a test file, e.g., test/app.test.js.
    Import Modules: Import Supertest and your Express app in the test file

  2. Mocking in Jest
    The purpose of mocking in Jest is that it enables testing of a unit of code independently of its dependencies to ensure that tests only concentrate on the logic of the code being tested. It gives one control over how dependencies behave, allowing them to test how their code reacts to various situations, including errors or specific return values.

Simulate Dependencies : We can use jest.mock to simulate dependencies on our tests by creating mock functions for modules or components, this will help us when we want to test a function or component in isolation without actually invoking the real dependencies.

Mocking examples in Express

// bookService.test.js
const { fetchBookById } = require('./bookService');
const booksDB = require('./booksDB');

describe('fetchBookById', () => {
it('should return book data for
a valid ID', async () => {
// Arrange: Set up the original implementation
const mockBook = { id: '1', title: '1984', author: 'George Orwell' };
jest.spyOn(booksDB, 'readById').mockResolvedValue(mockBook);

// **Act: Call the function**
const book = await fetchBookById('1');

// Assert: Verify the result
expect(book).toEqual(mockBook);

// Restore the original method
booksDB.readById.mockRestore();
});

it('should handle errors
gracefully', async () => {
// Arrange: Set up the mock to throw an error
const errorMessage = 'Book not found';
jest.spyOn(booksDB, 'readById').mockRejectedValue(new Error(errorMessage));

// **Act & Assert: Verify that the function handles the error**
await expect(fetchBookById('1')).rejects.toThrow(errorMessage);

// **Restore the original method**
booksDB.readById.mockRestore();

});

it('should return undefined when
readById returns undefined', async () => {
// Arrange: Temporarily replace the implementation
jest.spyOn(booksDB,
'readById').mockImplementation((id) => undefined);

// Act**: Call the function**
const book = await fetchBookById('1');

// Assert: **Verify the result**
expect(book).toBeUndefined();

// **Restore the original method**
booksDB.readById.mockRestore();

});
});

  1. Jest Expect function :
    You can use Jest’s expect function to perform assertions in your tests by writing test cases where you call expect with the actual value you want to test and then chain matchers to specify the expected outcome. Jest provides various matchers like toEqual, toBe, toContain, and toThrow to handle different types of assertions. The expect function helps compare the actual result with the expected value, ensuring your code behaves as intended.

Different types of assertions
toEqual:
Example: expect({ name: 'Alice' }).toEqual({ name: 'Alice' });
Use: Check deep equality for objects/arrays.

.toBe:
Example: expect(2 + 2).toBe(4);
Use: Check strict equality for primitives.

.toMatchObject:
Example: expect({ name: 'Alice', age: 25 }).toMatchObject({ name: 'Alice' });
Use: Check partial matches in objects.

.toContain:
Example: expect(['apple', 'banana']).toContain('banana');
Use: Check if an array contains an element.

  1. Hooks in Jest
    In Jest, Hooks are functions that let you run certain code at particular stages of the testing process, making it easier to efficiently set up and shut down test environments. There are 4 main hooks: beforeAll, beforeEach, AfterAll, AfterEach. They can be used to set up the required settings before tests begin and for the tidying up when tests are completed.

The difference between these hooks is that beforeAll and afterAll run once per test suite, while beforeEach and afterEach run before and after each individual test within the suite.

describe('User Service', () => {
beforeAll(() => {
// Set up a mock database connection once before all tests
return initializeMockDatabase();
});

beforeEach(() => {
// Reset the database to a known state before each test
return resetDatabase();
});

afterEach(() => {
// Clear any changes made during a test
return clearDatabaseChanges();
});

afterAll(() => {
// Close the mock database connection once after all tests
return closeMockDatabase();
});

test('should create a new user', () => {
// Test for creating a user
});

test('should retrieve a user by ID', () => {
// Test for retrieving a user
});

// More tests...
});

@mpho-oganne
Copy link

mpho-oganne commented Jul 23, 2024

Phamela, Nhlanhla, Mpho

  1. They make the test suite easier to read, maintain, and understand by creating a clear hierarchy of test groups and individual test cases.

describe Function
Groups Related Tests: Groups tests that are related to each other, making it clear what part of the code is being tested.
Creates Hierarchy: Allows nesting of describe blocks for more detailed organization.

it Function
Defines Individual Tests: Specifies individual test cases with clear descriptions.
Isolates Tests: Ensures each test is independent and checks a specific behavior.

  1. Supertest is a library used for testing HTTP endpoints in an Express.js application..

Step 1: Install Supertest
Add Supertest and a testing library (like Mocha or Jest) to your project to write tests for your HTTP endpoints.
Step 2: Set Up Your Express Application
Have an Express app with endpoints to test. For example, a /hello endpoint that responds with "Hello, world!".
Step 3: Create a Test File
In a new test file:
Import Supertest and your Express app.
Write tests to send HTTP requests
Step 4: Write a Test
In your test file:
Describe the endpoint (e.g., GET /hello).
Write a test case to send a request to the endpoint and check the response is as expected.
Step 5: Run Your Tests
Execute your tests using your chosen testing library. This will check if your endpoints respond correctly.

  1. Purpose of Mocking in Jest
    Mocking isolates the code being tested by simulating dependencies, making tests more reliable and faster.
    Using jest.mock
    jest.mock creates a mock version of a module, allowing you to control its behavior in your tests.

Example: Mocking A Database Module.

Database Module (db.js
// db.js
const getUserById = (id) => {
return { id, name: "John Doe" };
};
module.exports = { getUserById };

  1. Jest's expect function is used to perform assertions in your tests, allowing you to check whether the output of your code matches the expected values. Here are some examples of different types of assertions using Jest's expect function, along with explanations of when to use each:
    -. toBe()
    Use: toBe is used for primitive values like numbers, strings, and booleans. It uses Object.is for strict equality comparison.
  • toEqual()
    Use: toEqual is used for deep equality checks, making it suitable for objects and arrays. It recursively checks the properties or elements.
    When to Use: Use toEqual when comparing complex data structures like objects or arrays, where you need to ensure that all nested properties are equal.

  • toMatchObject()
    Use: toMatchObject is used for partial matching of objects. It checks that the specified properties exist and have the expected values in the object.
    When to Use: Use toMatchObject when you only care about some properties of an object and want to ensure they have specific values.

  • toContain()
    Use: toContain is used for checking if an item exists in an array or if a substring is present in a string.
    When to Use: Use toContain when you want to verify that an array includes a specific element or that a string contains a particular substring.

  1. Hooks are special functions that allow you to run code before or after certain events in the testing lifecycle, such as before or after each test or before and after a test suite. Hooks are particularly useful for setting up and tearing down test environments, initializing variables, or cleaning up after tests. They help ensure that your tests are isolated and run in a predictable environment.

Differences Between Hooks
Here's a summary of the differences between these hooks:
beforeAll: Runs once before all tests in a test suite.
afterAll: Runs once after all tests in a test suite.
beforeEach: Runs before each test in a test suite.
afterEach: Runs after each test in a test suite.

Scenario 1: Database Testing
beforeAll: Connect to the database once before running any tests.
afterAll: Disconnect from the database after all tests are complete.
beforeEach: Seed the database with fresh test data before each test.
afterEach: Clear the database entries created during each test.

Scenario 2: API Testing
beforeAll: Start a mock server before running any tests.
afterAll: Stop the mock server after all tests are complete.
beforeEach: Reset request handlers or mocks before each test.
afterEach: Clear any modified states or data after each test.
javascript

Scenario 3: UI Component Testing
beforeEach: Render a component before each test to ensure a fresh state.
afterEach: Unmount or clean up the component after each test.

@Gracepinkie
Copy link

Lindokuhle Skosana
Sinethemba Zulu
Wesley

  1. In Jest, the describe function groups related test cases together within a test file, providing a hierarchical structure that mirrors the organization of the code being tested. Each describe block contains it statements, which define individual test cases with clear descriptions of specific behaviors being tested. This approach enhances readability and maintainability by organizing tests into logical sections based on functionality or modules. describe allows for nested structures, enabling deeper levels of organization for complex test suites. Overall, describe and it functions in Jest facilitate systematic and focused testing, improving developers' ability to manage and debug code efficiently.

  2. Supertest provides a high-level abstraction for HTTP assertions, making it easy to write and maintain tests for your API routes.

steps:

a) Install Dependencies- npm install --save-dev supertest jest

b) Set Up Your Express Application
const express = require('express'); const app = express(); app.use(express.json());

c) Write Tests Using Supertest:
Create a test file (e.g., app.test.js) where you will write your test cases using Supertest.
javascript

d) Run Your Tests
Ensure your test script is set up in package.json:

{
"scripts": {
"test": "jest"
}
}

e) Run your tests using the following command:
npm test

  1. In Jest, mocking serves to isolate dependencies like databases in tests, ensuring focus on code behavior without external influences. jest.mock replaces modules with mocks, allowing control over return values and behaviors. For an Express.js app, mocking database.js in tests involves defining mock responses using jest.fn() for functions like getUsersFromDB. Tests then verify route handlers like getUsers respond correctly to mocked database behaviors, enhancing reliability and speed of tests while avoiding reliance on actual database connections.

example:
const database = require('./database');

async function getUsers(req, res) {
try {
const users = await database.getUsersFromDB(); // Function to fetch users from database
res.status(200).json(users);
} catch (error) {
console.error('Error fetching users:', error);
res.status(500).send('Error fetching users');
}
}

module.exports = { getUsers };

a) Database Module (database.js):

async function getUsersFromDB() {
// Logic to fetch users from the actual database
return await db.query('SELECT * FROM users');
}

module.exports = { getUsersFromDB };

b).Test File (routes.test.js)
const { getUsers } = require('./routes');
const database = require('./database'); // Mocking the database module
jest.mock('./database', () => ({ getUsersFromDB: jest.fn(), }));
describe('GET /users', () => { let req, res;
beforeEach(() => { req = {};
res = { status: jest.fn().mockReturnThis(), json: jest.fn(), send: jest.fn(), }; });

  1. In Jest, the expect function is used to make assertions in your tests, verifying that certain conditions are met during the execution of your code. Here are examples of different types of assertions supported by Jest's expect function, along with explanations of when to use each:
    a).toEqual
    The toEqual matcher checks for deep equality between the actual and expected values. It compares the properties and values of objects or arrays.

example

test('adding 1 + 2 equals 3', () => {
expect(1 + 2).toEqual(3);
});

test('object assignment', () => {
const data = { one: 1 };
data['two'] = 2;
expect(data).toEqual({ one: 1, two: 2 });
});

b) toBe

The toBe matcher checks for strict equality (===) between the actual and expected values. It's used for comparing primitive values like numbers, strings, booleans, or references (for object identity).

example :

test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});

test('checking for the exact equality of a reference', () => {
const value = {};
expect(value).toBe(value); // Same object reference
});

c) toMatchObject
The toMatchObject matcher checks that an object or array contains all the expected properties or elements, ignoring any extra properties.

Example:

test('toMatchObject example', () => {
const obj = {
name: 'John',
age: 30,
city: 'New York'
};
expect(obj).toMatchObject({
name: 'John',
age: 30
});
});

d)toThrow

The toThrow matcher is used to test if a function throws an error when it's called.

Example:

function throwError() {
throw new Error('This is an error');
}

test('throw error example', () => {
expect(throwError).toThrow();
expect(throwError).toThrow(Error);
expect(throwError).toThrow('This is an error');
expect(throwError).toThrow(/error/);
});

e). toHaveBeenCalled / toHaveBeenCalledWith

These matches are used to check if a mock function (or spy) was called during the test.

Example:

const mockFn = jest.fn();

test('mock function example', () => {
mockFn();
expect(mockFn).toHaveBeenCalled();

mockFn('hello', 'world');
expect(mockFn).toHaveBeenCalledWith('hello', 'world');
});

  1. Hooks in Jest help you manage the state and configuration needed before and after your tests run, ensuring that each test starts with a clean slate or has the necessary context.

a) beforeAll(fn): This hook runs once before all the tests in a describe block. It is useful for setting up a shared context or state that doesn't need to be reset between individual tests. it is used in setting up a database connection and Initializing a global variable or state.

b) beforeEach(fn): This hook runs before each individual test in a describe block. It is useful for setting up a consistent initial state for each test, ensuring that no test influences the outcome of another. It is used in resetting a database to a known state before each test, reinitializing mock data or state and setting up test-specific conditions.

c) afterAll(fn): This hook runs once after all the tests in a describe block have completed. It is useful for tearing down or cleaning up the context or state that was set up in beforeAll. It is used when closing a database connection, cleaning up global state or resources and performing any final cleanup after all tests have run.

d) afterEach(fn)
This hook runs after each individual test in a describe block. It is useful for cleaning up or resetting any state that should not persist between tests. It is used when clearing caches or temporary data. Releasing resources that are created for each test and ensuring that side effects from one test do not affect subsequent tests.

@NokulungaM
Copy link

NokulungaM commented Jul 23, 2024

@NokulungaM
@Tumelo2748
@Pumlanikewana

  1. In Jest, the describe and it functions are used to structure and define test cases.
    The describe and it functions in Jest help in creating a well-organized, maintainable, and readable test suite that clearly communicates the functionality being tested and the expected behavior of your code.
    eg:
    a) Logical Grouping: Organizes tests into related groups.
    b) Readable Output: Provides descriptive test output.
    c) Nesting: Allows for a detailed hierarchical structure.
    d) Shared Setup/Teardown: Facilitates common setup and teardown logic.
    e) Isolation: Ensures test suite isolation and reliability.

  2. Supertest is a powerful library for testing HTTP endpoints in Node.js applications, particularly with Express.js. It allows you to write tests that make requests to your Express app and assert the responses.

How to use Supertest for testing HTTP endpoints in an Express.js application:
a)Install Supertest and Jest: npm install supertest jest
b)Create Express App : Define your endpoints in an Express app.
c) Create a test file : Write tests using Supertest to send requests to your Express app.
d)Write Tests : Use Supertest to write tests for your endpoints.
e)Configure Jes t: Ensure your package.json is set up to use Jest.
f)Run Tests : Execute the tests with npm test.

Steps to setup Supertest:
a)Install Dependencies: Install Supertest and Jest using npm install supertest jest
b)Create or Identify Your Express App: Ensure you have an Express app set up.
c)Create a Test File: Write tests using Supertest to send requests to your Express app.
d)Configure Jest: Make sure Jest is set up in your package.json.

  1. Jest is a widely used JavaScript testing framework developed by Facebook. It’s designed to make writing and running tests more efficient and user-friendly. While originally intended for testing React applications, Jest is versatile enough to test any JavaScript codebase. Its intuitive design and robust features have contributed to its popularity among developers.

Simulating Dependencies with Jest.mock

Jest's jest.mock function enables you to replace external dependencies with controlled mock implementations during testing. This isolation enhances test reliability and speed by preventing unexpected behaviors from external systems.

Mocking in Jest is a technique to replace real functions or modules with simulated ones during testing.
Purpose:
Isolation: Focuses testing on a specific code unit without external dependencies.
Control: Manipulates input and output to test different scenarios.
Speed: Improves test execution time by avoiding slow operations.
Reliability: Reduces test flakiness by eliminating external factors.

Here's an example :

Create fake weather data: Make up weather information for your tests.
Test the code with the fake data: Check if the code handles different weather conditions correctly.
Only when everything works, test with real weather data: This is for final checks.

Example:

const fetchWeatherData = async (location) => {
  // Fetch weather data from a real API
};

// weatherService.test.js
const weatherService = require('./weatherService');

jest.mock('./weatherService');

test('fetchWeatherData', async () => {
  const dummyWeatherData = {
    city: 'New York',
    temperature: 75,
    condition: 'sunny',
    humidity: 50,
  };

  weatherService.fetchWeatherData.mockResolvedValue(dummyWeatherData);

  const data = await weatherService.fetchWeatherData('New York');

  expect(data).toEqual(dummyWeatherData);
});

4.Jest's expect function is the cornerstone of writing assertions in your tests. It takes a value and allows you to chain various matchers to check specific conditions.

toBe:Checks if values are exactly the same.

expect(2+2).toBe(4);

toEqual: Checks if values are equal, even for objects.
expect({name: 'John'}).toEqual({name: 'John'});

toMatch: Checks if a string matches a pattern.
expect('hello world').toMatch(/world/);

When to Use Which:
a) toBe: For primitive values (numbers, strings, booleans) and when strict equality is required.
b) toEqual: For objects and arrays when you care about the values, not the references.
c) toMatch: For string comparisons using regular expressions.
d) toBeTruthy/toBeFalsy: For checking if a value is truthy or falsy.
e) toContain: For checking if an item exists within an array or iterable.
f) toThrow: For testing if a function throws an expected error.

  1. Hooks in Jest are functions that allow you to run code at specific points in the testing lifecycle. They help in setting up and tearing down the environment before and after tests run.
    Hooks in Jest can be effectively used to set up and tear down test environments by organizing the code that initializes and cleans up resources needed for the tests.

The difference between hooks and the scenario's you can you use them in:
a) beforeAll (fn, timeout): This hook can be used to set up resources that are shared across all tests and only need to be initialized once. For example, you might start a database connection, initialize a server, or set up some shared state.

b) afterAll (fn, timeout): This hook is used to tear down the resources set up by beforeAll. For example, you might close the database connection, stop the server, or clean up shared state.

c) beforeEach (fn, timeout): This hook is used to set up the environment before each test runs. For example, you might reset a database to a known state, initialize mock data, or create new instances of objects needed for the tests.

d) afterEach (fn, timeout): This hook is used to clean up after each test runs. For example, you might clear mock data, reset configurations, or release resources that were allocated in beforeEach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment