Introduction

Unit testing is vital in software development, ensuring the accuracy and dependability of individual components within an application. When using NestJS and Prisma, a notable difficulty in unit testing emerges when handling databases. Establishing a dependable method for either mocking or seeding data becomes crucial to isolating tests and upholding uniformity across the entire test suite.

In this blog post, we'll explore centralized data mocking and seeding strategies for unit testing in a NestJS application using Prisma as the database ORM.

  1. Understanding the Importance of Data Mocking and Seeding: Unit tests should be isolated, meaning they don't depend on external factors such as databases or external APIs. In the context of NestJS and Prisma, this involves creating a controlled environment for your tests by either mocking database responses or seeding test data.
  2. Setting Up Prisma for Testing: In your NestJS project, create a separate database configuration specifically for testing. This ensures that your tests won't interfere with your production data. Use a testing database to run your unit tests and configure your Prisma client accordingly.

generator client {
  provider = "prisma-client-js"
  output   = "./src/prisma/testingClient"
}

datasource testing {
  provider = "sqlite"
  url      = "file:./test.db"
}

Prisma Schema

Creating Centralized Data Mocking Utilities

One approach is to create centralized utilities for data mocking and seeding. These utilities can be used across multiple tests to ensure consistency and reduce redundancy. Faker.js is a great way to generate realistic fake data for seeding your database during testing. Below is an example of incorporating Faker.js to create user data for seeding your user table.

Install faker:

npm install faker

In this example, the generateFakeUserData function uses Faker.js methods to create random names, email addresses, and other fields for each user.

// seeds/seed.ts

import { Prisma } from '../prisma/testingClient';
import faker from 'faker';

const generateFakeUserData = (): Prisma.UserCreateInput => {
  return {
    name: "TEST_DATA"+faker.name.findName(),
    email: faker.internet.email(),
    // Add more fields as needed
  };
};

export const seedDatabase = async () => {
  const seedData: Prisma.UserCreateInput[] = Array.from({ length: 10 }, generateFakeUserData);

  for (const userData of seedData) {
    await Prisma.user.create({ data: userData });
  }
};
export cont userDta=async ()=>{
return await Prisma.user.findFirst()
}

Prisma seed file for fake user data.

Modifying Unit Tests

Your unit tests remain mostly the same, with the seeding script executed before the tests start.

// tests/user.service.spec.ts

import { UserService } from '../src/user/user.service';
import { Prisma } from '../prisma/testingClient';
import { seedDatabase, userData } from './seeds/seed';

describe('UserService', () => {
  let userService: UserService;

  beforeEach(() => {
    userService = new UserService();
  });

  it('should retrieve a user by ID', async () => {
    const data=await userData()
    const userIdToRetrieve = data.id; // Assuming user ID 1 was seeded

    const retrievedUser = await userService.findById(userIdToRetrieve);

    expect(retrievedUser).toBeDefined();
    
  });

  
});

UserService tests for retrieving a user by ID

Seeding all necessary data before running tests brings efficiency and consistency to the unit testing process. By prepopulating the database with relevant data, the need to create information dynamically during test execution is eliminated, resulting in faster and more streamlined tests. This approach ensures a consistent environment across various test cases, promoting reliability and reproducibility in unit testing. Additionally, the ability to reuse seeded data facilitates the creation of standardized test scenarios, reducing redundancy and enabling thorough validation of different application functionalities. Ultimately, this practice enhances test coverage, allowing developers to comprehensively assess the application's behaviour under diverse conditions, leading to more robust and reliable software.

To manage running test files using the pretest hook in your npm scripts, you can create a setup that dynamically determines which test files to execute. Add a pretest script to your package.json file that runs a setup script to determine which test files to execute dynamically.

{
  "scripts": {
    "pretest": "node seeds/seed.ts",
    "test": "jest"
  }
}

content of package.json

Removing seeded data after the test is run:

Make a data clean-up file where you remove all seeded data. This file should run after running the test. You can use script hook posttest and update your package.json file for this.

// seeds/cleanup.ts

import { Prisma } from '../prisma/testingClient';
export cont userDelete=async ()=>{
return await Prisma.user.delete({where:{name:{startsWith:{"TEST_DATA"}}} })
}

Content of cleanup.ts

{
  "scripts": {
    "pretest": "node seeds/seed.ts",
    "posttest": "node seeds/cleanup.ts",
    "test": "jest"
  }
}

Content of package.json

Why Seeding Data Before Performing Test Is Important

Improved Test Performance: Seding data before performing tests allows tests to run more efficiently by eliminating the need to create data dynamically during test execution. This results in quicker test runs, enabling a faster feedback loop in the development process.

Consistency in Test Environments

Seeding data ensures a consistent and stable test environment. Tests can be executed with a known data set, reducing variability and providing a reliable baseline for comparing expected and actual outcomes.

Enhanced Test Reproducibility

By employing pre-seeded data, tests become highly reproducible. This enables testers and developers to consistently recreate particular scenarios, facilitating the identification and resolution of issues. The ability to reliably reproduce conditions under which problems arise proves invaluable in pinpointing and addressing issues effectively.

Reduced Test Redundancy

Seeding data allows for the creation of reusable test scenarios. Instead of recreating the same data for each test case, developers can leverage pre-existing seeded data, reducing redundancy and promoting a more streamlined and maintainable test suite.

Comprehensive Test Coverage

Seeding data enables testing various scenarios and edge cases, contributing to comprehensive test coverage. Developers can validate different aspects of the application's behaviour using a diverse set of pre-seeded data, ensuring robust testing across various conditions.

Time and Resource Efficiency

Pre-seeding data saves time and resources by creating data once and reusing it across multiple test cases. This efficiency is particularly beneficial in scenarios where generating data dynamically for each test would be resource-intensive and time-consuming.

Facilitates Integration Testing

Seeded data is especially valuable for integration testing, where interactions between different components must be tested. Having consistent and pre-existing data sets simplifies the setup for integration tests and ensures a smoother testing process.

Conclusion

Centralized data mocking and seeding in NestJS with Prisma can greatly enhance the efficiency and reliability of your unit tests. By following these practices, you can create a robust testing environment that ensures your application behaves as expected, even when interacting with databases. As your application evolves, these testing strategies will prove invaluable in maintaining a high level of code quality.

Thank you for reading this article. See you in the next one.