简体   繁体   中英

NestJS TypeORM mock a datasource of a repository

I am trying to mock out a repository. I don't want to do actual database calls. I (think I) am following the documentation on NestJS, and certain stackoverflow items.

However, when I run the test I get the following error:

JwtStrategy › validate › throws an unauthorized exception as user cannot be found
Nest can't resolve dependencies of the UserEntityRepository (?). Please make sure that the argument DataSource at index [0] is available in the TypeOrmModule context.

Potential solutions:
- If DataSource is a provider, is it part of the current TypeOrmModule?
- If DataSource is exported from a separate @Module, is that module imported within TypeOrmModule?
  @Module({
    imports: [ /* the Module containing DataSource */ ]
  })

Now as I understand, it seems that the UserEntityRepository is not properly mocked. As it is the first (index [0]) dependency in the user service class:

./user.service.ts

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

    async findOneBy({ username }): Promise<UserEntity> {
        return await this.userRepository.findOneBy({ username })
    }
}

./jwt.strategy.ts

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
      private userService: UserService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET || config.get('jwt.secret'),
    })
  }

  async validate(payload: JwtPayload) {
    const { username } = payload;
    const user = await this.userService.findOneBy({username});

    if (!user) {
      throw new UnauthorizedException();
    }

    return user;
  }
}

./jwt.strategy.spect.ts

const mockUserRepositoryFactory = jest.fn(() => ({
  findOneBy: jest.fn(entity => entity),
}));

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

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      imports: [UserModule],
      providers: [
        JwtStrategy,
        UserService,
        // shouldn't this correctly provide the datasource?
        {
          provide: getRepositoryToken(UserEntity),
          useFactory: mockUserRepositoryFactory,
        },

      ]
    }).compile();

    jwtStrategy = await module.get<JwtStrategy>(JwtStrategy);
    userService = await module.get<UserService>(UserService);
  });

  describe('validate', () => {
    it('validates and returns user based on JWT payload', async () => {
      const user = new UserEntity();
      user.username = 'TestUser';

      userService.findOneBy.mockResolvedValue(user);
      const result = await jwtStrategy.validate({ username: 'TestUser' });
      expect(userService.findOneBy).toHaveBeenCalledWith({ username: 'TestUser' });
      expect(result).toEqual(user);
    });

    it('throws an unauthorized exception as user cannot be found', async () => {
      userService.findOneBy.mockResolvedValue(null);
      expect(jwtStrategy.validate({ username: 'TestUser' })).rejects.toThrow(UnauthorizedException);
    });
  });
});

===== Update

Created a minimal setup in a Codesandbox.

https://codesandbox.io/s/xenodochial-benz-kve4eq?file=/test/jwt.test.js

But somehow the test tab isn't showing in the sandbox.

I have noticed that you are calling findOneBy in your service but mocked the findOne repository method.

Either you have to mock the findOne method of your service or findOneBy of your repository. In the current approach, you mock the repository. The solution for this use case would look like this:

const mockUserRepositoryFactory = jest.fn(() => ({
  // Change here from findOne to findOneBy
  findOneBy: jest.fn(entity => entity),
}));

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

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      imports: [UserModule],
      providers: [
        JwtStrategy,
        UserService,            {
          provide: getRepositoryToken(UserEntity),
          useFactory: mockUserRepositoryFactory,
        },

      ]
    }).compile();

    jwtStrategy = await module.get<JwtStrategy>(JwtStrategy);
    userService = await module.get<UserService>(UserService);
  });

  describe('validate', () => {
    it('validates and returns user based on JWT payload', async () => {
      const user = new UserEntity();
      user.username = 'TestUser';

      // change findOne to findOneBy here as well
      userService.findOneBy.mockResolvedValue(user);
      const result = await jwtStrategy.validate({ username: 'TestUser' });
      expect(userService.findOneBy).toHaveBeenCalledWith({ username: 'TestUser' });
      expect(result).toEqual(user);
    });

    it('throws an unauthorized exception as user cannot be found', async () => {
      userService.findOneBy.mockResolvedValue(null);
      expect(jwtStrategy.validate({ username: 'TestUser' })).rejects.toThrow(UnauthorizedException);
    });
  });
});

First, you have to mock DataSource (if you like you can create a separate file)

import { DataSource } from "typeorm";

// @ts-ignore
export const dataSourceMockFactory: () => MockType<DataSource> = jest.fn(() => ({
  <mock_function>: jest.fn(),
}));

export type MockType<T> = {
  [P in keyof T]?: jest.Mock<{}>;
};

Then create a test file

describe('---MSG---', () => {
...
  let dataSourceMock: MockType<DataSource>
...
  beforeAll(async () => {
    const module = await Test.createTestingModule({
      imports: [],
      controllers: [<CONTROLLERS>],
      providers: [ <PROVIDERS>
        { provide: DataSource, useFactory: dataSourceMockFactory }],
    }).compile()
...
    dataSourceMock = module.get(DataSource);
...
  })

  describe('---MSG---', () => {
    it('---MSG---', async () => {
      await <Call mock function>
      expect(dataSourceMock.<DataSource mocked function>).toBeCalled();
    });
  })

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM