Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 665
kyle Avatar asked Oct 13 '18 22:10

kyle


2 Answers

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.

like image 112
AyKarsi Avatar answered Oct 19 '22 22:10

AyKarsi


What @AyKarsi suggest is better than nothing, but it's still a bad practice.

Unit testing should mock databases and third party API calls.

Integration testing should test what has been mocked with the real database, and that part only.

End-to-end testing is there to check that the whole app is well connected altogether.

For more details, you can read : https://martinfowler.com/articles/practical-test-pyramid.html

like image 22
A Mehmeto Avatar answered Oct 19 '22 20:10

A Mehmeto