Search code examples
testingtddnestjstypeorm

Process of testing with TypeORM and Nestjs, and jest using mocks?


This question can likely be generalized to stubbing repositories in a service and how to properly test and provide coverage in the context of this question.

I am in the process of learning more about testing, but am stuck with how to properly perform testing that involves the DB.

I have a User entity that defines the columns and some initial validation logic.

    import { IsAlphanumeric, IsEmail, MinLength } from 'class-validator';
    import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
    @Entity()
    export class User {
      @PrimaryGeneratedColumn()
      public id!: number;

      @Column()
      public name!: string;

      @IsEmail()
      @Column()
      public email!: string;

      @MinLength(8)
      @Column()
      public password!: string;
    }

And I have a UserService that injects the Repository for the entity.

    import { Injectable } from '@nestjs/common';
    import { InjectRepository } from '@nestjs/typeorm';
    import { validateOrReject } from 'class-validator';
    import { Repository } from 'typeorm';
    import { CreateUserDTO } from './dto/create-user.dto';
    import { User } from './user.entity';

    @Injectable()
    export class UserService {
      constructor(
        @InjectRepository(User) private readonly userRepository: Repository<User>
      ) {}

      public async create(dto: CreateUserDTO) {
        const user = this.userRepository.create(dto);
        await validateOrReject(user);
        await this.userRepository.save(user);
      }

      public async findAll(): Promise<User[]> {
        return await this.userRepository.find();
      }

      public async findByEmail(email: string): Promise<User | undefined> {
        return await this.userRepository.findOne({
          where: {
            email,
          },
        });
      }
    }

And here is my preliminary test so you can follow my train of thought...

    import { Test, TestingModule } from '@nestjs/testing';
    import { getRepositoryToken } from '@nestjs/typeorm';
    import { User } from './user.entity';
    import { UserService } from './user.service';

    const createMock = jest.fn((dto: any) => {
      return dto;
    });

    const saveMock = jest.fn((dto: any) => {
      return dto;
    });

    const MockRepository = jest.fn().mockImplementation(() => {
      return {
        create: createMock,
        save: saveMock,
      };
    });
    const mockRepository = new MockRepository();

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

      beforeAll(async () => {
        const module: TestingModule = await Test.createTestingModule({
          providers: [
            UserService,
            {
              provide: getRepositoryToken(User),
              useValue: mockRepository,
            },
          ],
        }).compile();
        service = module.get<UserService>(UserService);
      });

      it('should be defined', () => {
        expect(service).toBeDefined();
      });

      it('should not create invalid user', async () => {
        // ??
      });
    });

So while I can make the test run and everything, I am not sure what I am actually supposed to be testing. I can obviously test that it validates on create, and for other things like findAll, I feel like I am just mocking the database? For me to properly test this, would it need to be connected to a database so I can check that the right data is returned?

The nest documents say "we usually want to avoid any database connection", but doesn't doing that defeat the purpose since we aren't really testing the functionality? Because while I can mock that the save returns a value, I am not testing for any errors that can occur with unique columns, nullable data, incrementing values to be set, etc... right?


Solution

  • Many see it as bad practice to test against a db. But for exactly the reasons you mention + saving myself the hassle of managing the mocks and stubs, I nearly always run my tests against a dedicated test-database.

    In my jest start-up I clear out all tables and then have helpers which help me create entities with relations as needed, to ensure that my test remain atomic.