Skip to content

Test Template

This template provides a standard structure for creating new test files in the PRS system using Mocha, Chai, and Sinon.

Usage Instructions

  1. Replace EntityName with your entity name
  2. Implement the test cases
  3. Run the tests with npm test

Template Code

JavaScript
/**
 * Template for creating a new test file
 * 
 * Instructions:
 * 1. Replace EntityName with your entity name
 * 2. Implement the test cases
 * 3. Run the tests with npm test
 */
const { expect } = require('chai');
const sinon = require('sinon');
const { createContainer } = require('awilix');

// Import the class to test
const EntityNameService = require('../../src/app/services/entityNameService');

describe('EntityNameService', () => {
  let entityNameService;
  let entityNameRepositoryMock;
  let dbMock;
  let constantsMock;
  let clientErrorsMock;
  let container;

  beforeEach(() => {
    // Create mocks
    entityNameRepositoryMock = {
      findAll: sinon.stub(),
      findOne: sinon.stub(),
      create: sinon.stub(),
      update: sinon.stub(),
      destroy: sinon.stub(),
    };

    dbMock = {
      sequelize: {
        transaction: sinon.stub().resolves({
          commit: sinon.stub().resolves(),
          rollback: sinon.stub().resolves(),
        }),
      },
      Sequelize: {
        Op: {
          in: Symbol('in'),
          gt: Symbol('gt'),
          lt: Symbol('lt'),
          gte: Symbol('gte'),
          lte: Symbol('lte'),
          like: Symbol('like'),
        },
      },
    };

    constantsMock = {
      entityName: {
        STATUS: {
          ACTIVE: 'ACTIVE',
          INACTIVE: 'INACTIVE',
          PENDING: 'PENDING',
        },
        TYPE: {
          TYPE_A: 'TYPE_A',
          TYPE_B: 'TYPE_B',
          TYPE_C: 'TYPE_C',
        },
      },
    };

    clientErrorsMock = {
      BAD_REQUEST: sinon.stub().returns(new Error('Bad Request')),
      NOT_FOUND: sinon.stub().returns(new Error('Not Found')),
      FORBIDDEN: sinon.stub().returns(new Error('Forbidden')),
    };

    // Create service instance with mocks
    entityNameService = new EntityNameService({
      entityNameRepository: entityNameRepositoryMock,
      db: dbMock,
      constants: constantsMock,
      clientErrors: clientErrorsMock,
    });
  });

  afterEach(() => {
    // Reset all stubs
    sinon.restore();
  });

  describe('getAll', () => {
    it('should return all entities with default pagination', async () => {
      // Arrange
      const expectedResult = {
        data: [
          { id: 1, name: 'Entity 1' },
          { id: 2, name: 'Entity 2' },
        ],
        meta: {
          page: 1,
          limit: 10,
          total: 2,
        },
      };

      entityNameRepositoryMock.findAll.resolves(expectedResult);

      // Act
      const result = await entityNameService.getAll();

      // Assert
      expect(result).to.deep.equal(expectedResult);
      expect(entityNameRepositoryMock.findAll.calledOnce).to.be.true;
      expect(entityNameRepositoryMock.findAll.firstCall.args[0]).to.deep.include({
        page: 1,
        limit: 10,
        order: [['createdAt', 'DESC']],
      });
    });

    // Additional test cases...
  });

  // Additional test methods...
});

Key Features

  1. Testing Framework: Uses Mocha as the test runner
  2. Assertions: Uses Chai for assertions
  3. Mocking: Uses Sinon for mocking and stubbing
  4. Dependency Injection: Uses Awilix for dependency injection
  5. Arrange-Act-Assert Pattern: Follows the AAA pattern for test structure

Test Structure

  1. Describe Blocks: Group tests by class and method
  2. Before/After Hooks: Set up and tear down test environment
  3. Mock Creation: Create mocks for dependencies
  4. Test Cases: Individual test cases for different scenarios
  5. Assertions: Verify expected behavior

Common Test Scenarios

Testing Service Methods

JavaScript
describe('getById', () => {
  it('should return entity when found', async () => {
    // Arrange
    const entityId = 1;
    const expectedEntity = { id: 1, name: 'Entity 1' };

    entityNameRepositoryMock.findOne.resolves(expectedEntity);

    // Act
    const result = await entityNameService.getById(entityId);

    // Assert
    expect(result).to.deep.equal(expectedEntity);
    expect(entityNameRepositoryMock.findOne.calledOnce).to.be.true;
    expect(entityNameRepositoryMock.findOne.firstCall.args[0]).to.deep.include({
      where: { id: entityId },
    });
  });

  it('should return null when entity not found', async () => {
    // Arrange
    const entityId = 999;

    entityNameRepositoryMock.findOne.resolves(null);

    // Act
    const result = await entityNameService.getById(entityId);

    // Assert
    expect(result).to.be.null;
    expect(entityNameRepositoryMock.findOne.calledOnce).to.be.true;
  });
});

Testing Controller Methods

JavaScript
describe('EntityNameController', () => {
  describe('getAll', () => {
    it('should return 200 with entities', async () => {
      // Arrange
      const request = {
        query: {},
        log: { error: sinon.stub() },
      };

      const reply = {
        status: sinon.stub().returnsThis(),
        send: sinon.stub(),
      };

      const entities = {
        data: [
          { id: 1, name: 'Entity 1' },
          { id: 2, name: 'Entity 2' },
        ],
        meta: {
          page: 1,
          limit: 10,
          total: 2,
        },
      };

      entityNameServiceMock.getAll.resolves(entities);

      // Act
      await entityNameController.getAll(request, reply);

      // Assert
      expect(reply.status.calledWith(200)).to.be.true;
      expect(reply.send.calledWith(entities)).to.be.true;
    });
  });
});

Best Practices

  1. Use the Arrange-Act-Assert pattern
  2. Mock all dependencies
  3. Test both success and error cases
  4. Reset mocks between tests
  5. Use descriptive test names
  6. Test one thing per test case
  7. Avoid testing implementation details