Reputation: 5367
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.
Upvotes: 3
Views: 13076
Reputation: 154
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();
});
})
Upvotes: 6
Reputation: 2061
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);
});
});
});
Upvotes: 1