Create a document in beforeEach on Jest nest.js - testing

I'm Using the in memory mongoose database for create my Unit test, and I want to create a document before the tests.
My interface is:
export interface IUsers {
readonly name: string;
readonly username: string;
readonly email: string;
readonly password: string;
}
and my beforeEach is:
import { MongooseModule } from "#nestjs/mongoose";
import { Test, TestingModule } from "#nestjs/testing";
import { closeInMongodConnection, rootMongooseTestModule } from '../test-utils/mongo/MongooseTestModule';
import { User, UserSchema } from "./schemas/users.schema";
import { UsersService } from "./users.service";
describe("UsersService", () => {
let service: UsersService;
let testingModule: TestingModule;
let userModel: Model<User>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
rootMongooseTestModule(),
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
],
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
//create user
userModel = testingModule.get<Model<User>>(
'UserModel',
);
});
I get an error TypeError: Cannot read pro perties of undefined (reading 'get') during the test. I tried to use let userModel: Model<IUsers>; But I get the same error.

Use either testingModule or module.
You declared testingModule but never initialized.
let testingModule: TestingModule; This part is undefined unless something is assigned to it.
Try like this
describe('UsersService', () => {
let testingModule: TestingModule;
let userModel: Model<User>;
let userService: UserService;
beforeEach(async () => {
testingModule = await Test.createTestingModule({
imports: [
rootMongooseTestModule,
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
providers: [UsersService],
}).compile();
userService = testingModule.get<UsersService>(UsersService);
userModel = testingModule.get<Model<User>>('UserModel');
// await userModel.create(...) or whatever methods you have
});
});

Related

NestJS - No metadata for "EmployeeRepository" was found with authentication

I try to do some authentication for my nestjs app but I got stuck with this error and I don't know where to look at
My app.module.ts
#Module({
imports: [
AgenciesModule,
ActionsModule,
AuthModule,
TypeOrmModule.forRoot({
type: 'mysql',
host: '************',
username: '*********',
password: '*********',
database: '*********',
synchronize: true,
autoLoadEntities: true,
}),
EmployeesModule,
],
})
export class AppModule {}
My auth.service.ts
import { EmployeeRepository } from 'src/employees/entities/employee.repository';
#Injectable()
export class AuthService {
constructor(
#InjectRepository(EmployeeRepository)
private employeeRepository: EmployeeRepository,
) {}
async validateUser(email: string, password: string): Promise<any> {
const user = await this.employeeRepository.findOne({
where: { email, password },
});
// this work with postman if I put false data
//const user = {
// email: "email",
// password: "password",
//}
if (user && user.email === email && user.password === password) {
const { password, ...result } = user;
return result;
}
return null;
}
}
My auth.controller.ts
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
#Post('login')
async login(#Body() body) {
return this.authService.login(body.email, body.password);
}
}
My auth.module.ts
imports: [TypeOrmModule.forFeature([EmployeeRepository])],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
And my employee.repository.ts
import { EntityRepository, Repository } from 'typeorm';
import { Employee } from './employee.entity';
#EntityRepository(Employee)
export class EmployeeRepository extends Repository<Employee> {}
I didn't put the different import for each file but I can provide them if needed
I checked all the file name and path from the differents import and they are all correct and I also updated my packages just in case.
These posts dosen't help :
NestJS - No metadata for "<Entity>" was found
No metadata for "User" was found using TypeOrm
Try this in auth.module.ts
imports: [TypeOrmModule.forFeature([EmployeeRepository])]
Change like this
imports: [TypeOrmModule.forFeature([Employee])]

Nestjs pipe works when I manually create entity but not in jest test

I have a validation pipe to check input that works when I manually create a product(using postman), but it doesn't check when I run tests. any explanations?
my validator:
#Injectable()
export class JoiValidationPipe implements PipeTransform {
constructor(private schema: ObjectSchema) {}
transform(value: any, metadata: ArgumentMetadata) {
const { error } = this.schema.validate(value);
if (error) {
throw new HttpException('Validation failed', HttpStatus.BAD_REQUEST);
}
return value;
}
}
my controller:
#UsePipes(new JoiValidationPipe(productSchema))
#Post()
async create(#Body() createProductDto: CreateProductDto): Promise<Product> {
return (await this.productsService.create(createProductDto)).product;
}
my test:
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ProductsController],
providers: [ProductsService],
}).compile();
controller = module.get<ProductsController>(ProductsController);
service = module.get<ProductsService>(ProductsService);
});
describe('create()', () => {
it('should fail to add a new product', async () => {
const result: Product = {
name: 'p',
price: -100,
category: 'junk',
};
expect(await controller.create(result)).toBe(result);
});
});
my schema:
export const productSchema: ObjectSchema = object({
createProductDto: object().keys({
name: string().min(5).required(),
price: number().integer().min(0).default(0),
category: string().min(5).required(),
}),
});
Pipes don't run unless you're going through the HTTP request. Same for other enhancers like guards and interceptors. If you want to test the pipe you can do that with supertest and e2e tests, or you can test the schema directly with joi in a different test suite

NestJs - Jest - Testing: ConnectionNotFoundError: Connection "default" was not found

I am trying to write a test of a simple TODO App.
This is my service test class:
const mockTaskRepository = () => ({
createTask: jest.fn(),
});
describe('TasksService', () => {
let tasksService;
let taskRepository;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [TasksService, { provide: TaskRepository, useFactory: mockTaskRepository }],
}).compile();
tasksService = await module.get<TasksService>(TasksService);
taskRepository = await module.get<TaskRepository>(TaskRepository);
});
describe('create task', () => {
it('calls taskRepository.create() and returns the result', async () => {
const createTaskDto = {
title: 'Test task',
description: 'Test desc',
};
taskRepository.createTask.mockResolvedValue('someTask');
expect(taskRepository.createTask).not.toHaveBeenCalled();
const result = await tasksService.createTask(createTaskDto);
expect(taskRepository.createTask).toHaveBeenCalledWith(createTaskDto);
expect(result).toEqual('someValue');
});
});
});
And this is my task service:
#Injectable()
export class TasksService {
constructor(
#InjectRepository(TaskRepository)
private taskRepository: TaskRepository,
) {}
async createTask(createTaskDto: CreateTaskDto): Promise<Task> {
const { title, description } = createTaskDto;
const task = new Task();
task.title = title;
task.description = description;
task.status = TaskStatus.IN_PROGRESS;
await task.save();
return task;
}
}
When I try to run the Create a Task test, the error below occurs.
FAIL src/tasks/tasks.service.spec.ts
● TasksService › create task › calls taskRepository.create() and returns the result
ConnectionNotFoundError: Connection "default" was not found.
at new ConnectionNotFoundError (error/ConnectionNotFoundError.ts:8:9)
at ConnectionManager.Object.<anonymous>.ConnectionManager.get (connection/ConnectionManager.ts:40:19)
at Object.getConnection (index.ts:247:35)
at Function.Object.<anonymous>.BaseEntity.getRepository (repository/BaseEntity.ts:85:72)
at Task.Object.<anonymous>.BaseEntity.save (repository/BaseEntity.ts:50:42)
at TasksService.createTask (tasks/tasks.service.ts:35:14)
at Object.<anonymous> (tasks/tasks.service.spec.ts:69:38)
Test Suites: 1 failed, 1 passed, 2 total
Tests: 1 failed, 8 passed, 9 total
Anyone know what's the mistake in the code?
..........................................
Thanks in advance!
If it is looking for connection default then I assume that the TypeOrmModule configs are still being taken into account. Instead of provide: TaskRepository try changing it to provide: getRepositoryToken(TaskEntity) if you have a TaskEntity. This will tell Nest to override the default repo that the #InjectRepository() decorator tries to provide.
If that isn't the case, do you think you could add your TaskService and TaskModule classes as well?
You should mock the repository then bringing up the context of Test Module. Otherwise, the real Repository is being injected (via Nest's DI) into service.
const module = await Test.createTestingModule({
providers: [
{
provide: getRepositoryToken(YourEntityClass),
useValue: mockedRepo, // or use class
},
TasksService],
}).compile();
tasksService = await module.get<TasksService>(TasksService);
// taskRepository = await module.get<TaskRepository>(TaskRepository); don't have to do that if `useValue` was used instead of factory
So, TL;DR :
provide: TaskRepository should provide Token of given Injectable in this case: https://docs.nestjs.com/fundamentals/custom-providers#non-class-based-provider-tokens
The test expected method Repository.createTask to be called, but method Service.createTask didn't make that call.
The fix: Update the Service method to delegate to the Repository method the task creation.
// tasks.service.ts
async createTask(createTaskDto: CreateTaskDto, user: User): Promise<Task> {
return this.taskRepository.createTask(createTaskDto, user);
}
// tasks.repository.ts
async createTask(createTaskDto: CreateTaskDto, user: User): Promise<Task> {
const { title, description } = createTaskDto;
const task = new Task();
task.title = title;
task.user = user;
task.description = description;
task.status = TaskStatus.IN_PROGRESS;
await task.save();
delete task.user;
return task;
}
}

NestJS how to create new mongoose Model in unittest?

I try to unittest my NestJs Controller class. I already mocked my Service using ts-mockito but now I struggle to create the mongoose Objects I want to return and expect to get returned by the controller. How do I manage to create new Model Object to test with?
this is my Service:
#Injectable()
export class ProjectService {
constructor(
#InjectModel('Project') private readonly projectModel: Model<Project>,
private tagService: TagService,
) {} ...
This is my Model
let schema = new Schema({
name: {type: String, required: true},
description: String,
created: {type: Date, default: Date.now},
});
export const ProjectSchema = schema;
export interface Project extends Document {
readonly name: string,
readonly description: string,
readonly created: Date,
}
And this is my Module:
#Module({
imports: [
MongooseModule.forFeature([{ name: 'Project', schema: ProjectSchema }]),
],
controllers: [
ProjectController
],
providers: [
ProjectService,
],
})
export class ProjectModule {}
This is my Test:
describe('ProjectController', async () => {
let projectController: ProjectController;
let projectServiceMock: ProjectService = mock(ProjectService);
let projectModel: Model<Project>;
beforeAll(async () => {
projectModel = mock(Model);
const module: TestingModule = await Test.createTestingModule({
controllers: [ProjectController],
providers: [
{
provide: ProjectService,
useValue: instance(projectServiceMock)
},
{
provide: 'Project',
useValue: instance(projectModel)
}
]
}).compile();
projectController = module.get<ProjectController>(ProjectController);
});
Now I am trying to create a new Object of Project and return it from my service and expect it from the controller:
it('should return Project with id from projectService', async () => {
const project = new projectModel({name: 'ProjectName', description: 'ProjectDescription'});
let result = Promise.resolve(project);
when(projectServiceMock.getById('projectId')).thenReturn(result);
await expect(projectController.getById('projectId')).toEqual(result);
});
But I get this error:
Nest cannot find given element (it does not exist in current context)
25 |
26 | projectController = module.get<ProjectController>(ProjectController);
> 27 | projectModel = module.get<Model<Project>>('Project');
| ^
28 | });
29 |
30 | describe('getAll', async () => {
As I think I can read from the error message there must be something wrong with 'getting' the model to the test but I really don't know how I can get the Model without initiating a connection or so...
What can I do? Do you have some example code that worked for you?
Use getModelToken function exposed in #nestjs/mongoose:
import {getModelToken} from '#nestjs/mongoose';
const module = await Test.createTestingModule({
providers: [
{
provide: getModelToken('ModelName'),
useValue: ModelMock,
},
],
}).compile()
modelMock = module.get<mongoose.Model<any>>('ModelNameModel'); // The getModelFunction just appends 'Model' to the Model name

How override Provider in Angular 5 for only one test?

In one of my unit test files, I have to mock several times the same service with different mocks.
import { MyService } from '../services/myservice.service';
import { MockMyService1 } from '../mocks/mockmyservice1';
import { MockMyService2 } from '../mocks/mockmyservice2';
describe('MyComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
MyComponent
],
providers: [
{ provide: MyService, useClass: MockMyService1 }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MapComponent);
mapComponent = fixture.componentInstance;
fixture.detectChanges();
});
describe('MyFirstTest', () => {
it('should test with my first mock', () => {
/**
* Test with my first mock
*/
});
});
describe('MySecondTest', () => {
// Here I would like to change { provide: MyService, useClass: MockMyService1 } to { provide: MyService, useClass: MockMyService2 }
it('should test with my second mock', () => {
/**
* Test with my second mock
*/
});
});
});
I see that the function overrideProvider exists, but I did not manage to use it in my test. When I use it in a "it", the provider doesn't change. I didn't manage to find an example where this function is called. Could you explain me how to use it properly? Or have you an other method to do that?
As of angular 6 I noticed that overrideProvider works with the useValue property. So in order to make it work try something like:
class MockRequestService1 {
...
}
class MockRequestService2 {
...
}
then write you TestBed like:
// example with injected service
TestBed.configureTestingModule({
// Provide the service-under-test
providers: [
SomeService, {
provide: SomeInjectedService, useValue: {}
}
]
});
And whenever you want to override the provider just use:
TestBed.overrideProvider(SomeInjectedService, {useValue: new MockRequestService1()});
// Inject both the service-to-test and its (spy) dependency
someService = TestBed.get(SomeService);
someInjectedService = TestBed.get(SomeInjectedService);
Either in a beforeEach() function or place it in an it() function.
If you need TestBed.overrideProvider() with different values for different test cases, TestBed is frozen after call of TestBed.compileComponents() as #Benjamin Caure already pointed out. I found out that it is also frozen after call of TestBed.get().
As a solution in your 'main' describe use:
let someService: SomeService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{provide: TOKEN, useValue: true}
]
});
// do NOT initialize someService with TestBed.get(someService) here
}
And in your specific test cases use
describe(`when TOKEN is true`, () => {
beforeEach(() => {
someService = TestBed.get(SomeService);
});
it(...)
});
describe(`when TOKEN is false`, () => {
beforeEach(() => {
TestBed.overrideProvider(TOKEN, {useValue: false});
someService = TestBed.get(SomeService);
});
it(...)
});
If the service is injected as public property, e.g.:
#Component(...)
class MyComponent {
constructor(public myService: MyService)
}
You can do something like:
it('...', () => {
component.myService = new MockMyService2(...); // Make sure to provide MockMyService2 dependencies in constructor, if it has any.
fixture.detectChanges();
// Your test here...
})
If injected service is stored in a private property, you can write it as (component as any).myServiceMockMyService2 = new MockMyService2(...); to bypass TS.
It's not pretty but it works.
As for TestBed.overrideProvider, I had no luck with that approach (which would be much nicer if it worked):
it('...', () =>{
TestBed.overrideProvider(MyService, { useClass: MockMyService2 });
TestBed.compileComponents();
fixture = TestBed.createComponent(ConfirmationModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
// This was still using the original service, not sure what is wrong here.
});
I was facing similar problem, but in a simpler scenario, just one test(describe(...)) with multiple specifications(it(...)).
The solution that worked for me was postponing the TestBed.compileComponents and the TestBed.createComponent(MyComponent) commands.
Now I execute those on each individual test/specification, after calling TestBed.overrideProvider(...) when needed.
describe('CategoriesListComponent', () => {
...
beforeEach(async(() => {
...//mocks
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])],
declarations: [CategoriesListComponent],
providers: [{provide: ActivatedRoute, useValue: mockActivatedRoute}]
});
}));
...
it('should call SetCategoryFilter when reload is false', () => {
const mockActivatedRouteOverride = {...}
TestBed.overrideProvider(ActivatedRoute, {useValue: mockActivatedRouteOverride });
TestBed.compileComponents();
fixture = TestBed.createComponent(CategoriesListComponent);
fixture.detectChanges();
expect(mockCategoryService.SetCategoryFilter).toHaveBeenCalledTimes(1);
});
Just for reference, if annynone meets this issue.
I tried to use
TestBed.overrideProvider(MockedService, {useValue: { foo: () => {} } });
it was not working, still the original service was injected in test (that with providedIn: root)
In test I used alias to import OtherService:
import { OtherService } from '#core/OtherService'`
while in the service itself I had import with relative path:
import { OtherService } from '../../../OtherService'
After correcting it so both test and service itself had same imports TestBed.overrideProvider() started to take effect.
Env: Angular 7 library - not application and jest
I needed to configure MatDialogConfig for two different test scenarios.
As others pointed out, calling compileCompents will not allow you to call overrideProviders. So my solution is to call compileComponents after calling overrideProviders:
let testConfig;
beforeEach(waitForAsync((): void => {
configuredTestingModule = TestBed.configureTestingModule({
declarations: [MyComponentUnderTest],
imports: [
MatDialogModule
],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: { testConfig } }
]
});
}));
const buildComponent = (): void => {
configuredTestingModule.compileComponents(); // <-- compileComponents here
fixture = TestBed.createComponent(MyComponentUnderTest);
component = fixture.componentInstance;
fixture.detectChanges();
};
describe('with default mat dialog config', (): void => {
it('sets the message property in the component to the default', (): void => {
buildComponent(); // <-- manually call buildComponent helper before each test, giving you more control of when it is called.
expect(compnent.message).toBe(defaultMessage);
});
});
describe('with custom config', (): void => {
const customMessage = 'Some custom message';
beforeEach((): void => {
testConfig = { customMessage };
TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: testConfig }); //< -- override here, before compiling
buildComponent();
});
it('sets the message property to the customMessage value within testConfig', (): void => {
expect(component.message).toBe(customMessage);
});
});