I am using jest for unit tests. And I get this error when tests are run for auth.controller.spec.ts (src/auth/test/auth.controller.spec.ts).
app.module
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { AuthModule } from './auth/auth.module';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
ConfigModule.forRoot(),
MongooseModule.forRootAsync({
useFactory: async () => ({
uri: process.env.DB_URI,
useNewUrlParser: true,
useUnifiedTopology: true,
}),
}),
UsersModule,
AuthModule
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }
users.module
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { MongooseModule } from '@nestjs/mongoose';
import { User, UserSchema } from './schemas/user.schema';
import { UsersRepository } from './users.repository';
@Module({
imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
controllers: [UsersController],
providers: [UsersService, UsersRepository],
exports: [UsersService]
})
export class UsersModule { }
users.service
import { Injectable } from '@nestjs/common';
import { FilterQuery } from 'mongoose';
import { CreateUserDto } from './dtos/createUserDto.dto';
import { UpdateUserDto } from './dtos/updateUserDto.dto';
import { User } from './schemas/user.schema';
import { UsersRepository } from './users.repository';
@Injectable()
export class UsersService {
constructor(private readonly UsersRepository: UsersRepository) { }
async findOne(userFilterQuery: FilterQuery<User>): Promise<User> {
return this.UsersRepository.findOne(userFilterQuery);
}
async createUser(createUserDto: CreateUserDto): Promise<User> {
return this.UsersRepository.create(createUserDto);
}
async updateUser(userId: string, updateUserDto: UpdateUserDto): Promise<User> {
return this.UsersRepository.findOneAndUpdate({ userId }, updateUserDto);
}
}
auth.module
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { MongooseModule } from '@nestjs/mongoose';
import { UsersRepository } from '../users/users.repository';
import { User, UserSchema } from '../users/schemas/user.schema';
import { UsersModule } from '../users/users.module';
import { UsersService } from '../users/users.service';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
@Module({
imports: [
UsersModule,
JwtModule.registerAsync({
useFactory: () => ({
secret: process.env.SECRET,
signOptions: { expiresIn: '3600s' }
})
}),
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])
],
controllers: [AuthController],
providers: [AuthService, UsersService, UsersRepository]
})
export class AuthModule { }
auth.service
import { Injectable } from '@nestjs/common';
import { HttpException } from '@nestjs/common/exceptions';
import { HttpStatus } from '@nestjs/common/enums';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import { CreateUserDto } from '../users/dtos/createUserDto.dto';
import { User } from '../users/schemas/user.schema';
import { ExistingUserDto } from '../users/dtos/existingUserDto.dto';
@Injectable()
export class AuthService {
constructor(
private UserService: UsersService,
private jwtService: JwtService,
) { }
async hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 12);
}
async register(createUser: Readonly<CreateUserDto>): Promise<User> {
const newUser = { ...createUser };
console.log(newUser);
const userExists = await this.UserService.findOne({ email: newUser.email });
console.log(userExists);
if (userExists) {
throw new HttpException(
'An account with that email already exists!',
HttpStatus.CONFLICT,
);
}
const hashedPassword: string = await this.hashPassword(newUser.password);
newUser.password = hashedPassword;
return this.UserService.createUser(newUser);
}
async validateUser(user: ExistingUserDto): Promise<User | null> {
const userExists: User = await this.UserService.findOne({ email: user.email })
if (!userExists) return null;
const passwordMatched: boolean = await this.doesPasswordMatch(user.password, userExists.password);
if (!passwordMatched) return null;
console.log(userExists)
return userExists;
}
async doesPasswordMatch(password: string, hashedPassword: string): Promise<boolean> {
return bcrypt.compare(password, hashedPassword);
}
async login(user: ExistingUserDto): Promise<{ token: string } | null> {
const userIsValid = await this.validateUser(user);
console.log(userIsValid);
if (!userIsValid) throw new HttpException("Credentials Invalid", HttpStatus.UNAUTHORIZED);
const jwt: string = await this.jwtService.signAsync(userIsValid);
return { token: jwt };
}
}
auth.controller.ts
import { Controller, Post, Body, Get } from '@nestjs/common';
import { CreateUserDto } from '../users/dtos/createUserDto.dto';
import { ExistingUserDto } from '../users/dtos/existingUserDto.dto';
import { User } from '../users/schemas/user.schema';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authServices: AuthService) { }
@Get()
getHello(): string {
return process.env.DB_URI;
}
@Post('register')
async register(@Body() user: CreateUserDto): Promise<User> {
return this.authServices.register(user);
}
@Post('login')
async login(@Body() user: ExistingUserDto): Promise<{ token: string } | null> {
console.log(user);
return this.authServices.login(user);
}
}
auth.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersModule } from '../../users/users.module';
import { User } from '../../users/schemas/user.schema';
import { UsersService } from '../../users/users.service';
import { AuthController } from '../auth.controller';
import { AuthService } from '../auth.service';
import { UserStub } from './stubs/user.stub';
jest.mock('../auth.service');
jest.mock('../../users/users.service');
describe('AuthController', () => {
let authController: AuthController;
let authService: AuthService;
let usersService: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [UsersModule],
controllers: [AuthController],
providers: [AuthService, UsersService]
}).compile();
authController = module.get<AuthController>(AuthController);
authService = module.get<AuthService>(AuthService);
usersService = module.get<UsersService>(UsersService);
jest.clearAllMocks();
});
describe('register', () => {
describe('When register is called', () => {
let user: Omit<User, "password">;
beforeEach(async () => {
user = await authController.register(UserStub())
})
test('then it should call authService.register', () => {
expect(authService.register).toBeCalledWith(UserStub());
})
test('where usersService.createUser is called', () => {
expect(usersService.createUser).toBeCalledWith(UserStub());
})
test('then it should return a user', () => {
expect(user).toEqual(UserStub());
})
})
})
});
auth/mocks/auth.service.ts
import { UserStub } from "../test/stubs/user.stub";
export const AuthService = jest.fn().mockReturnValue({
hashPassword: jest.fn().mockResolvedValue(''),
register: jest.fn().mockResolvedValue(UserStub()),
validateUser: jest.fn().mockResolvedValue(UserStub() || null),
login: jest.fn().mockResolvedValue({ token: '' }),
doesPasswordMatch: jest.fn().mockResolvedValue(true)
})
users/mocks/users.service.ts
import { UserStub } from "../../auth/test/stubs/user.stub";
export const UsersService = jest.fn().mockReturnValue({
findOne: jest.fn().mockResolvedValue(UserStub()),
createUser: jest.fn().mockResolvedValue(UserStub()),
updateUser: jest.fn().mockResolvedValue(UserStub())
})
