I wrote a test for a NestJS Controller that was working somehow, but now I get this error:
describe('ModalityController', () => {
let controller: ModalityController;
let modalitiesFactory: Modality[];
const mockService = {
getAll: jest.fn(),
createOne: jest.fn(),
updateOne: jest.fn(),
};
beforeAll(async (done) => {
await useSeeding();
modalitiesFactory = await factory(Modality)().makeMany(10);
done();
});
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ModalityController],
providers: [
{
provide: 'Connection',
useValue: {},
},
{
provide: ModalityService,
useValue: mockService,
},
],
}).compile();
controller = module.get<ModalityController>(ModalityController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
I get this error:
ModalityController
× should be defined (21 ms)
● ModalityController › should be defined
Nest can't resolve dependencies of the de8e6b44-c5c0-4633-b3b4-638e9df1b2a3 (?). Please make sure that the argument Connection at index [0] is available in the RootTestModule context.
Potential solutions:
- If Connection is a provider, is it part of the current RootTestModule?
- If Connection is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing Connection */ ]
})
at Injector.lookupComponentInParentModules (../node_modules/#nestjs/core/injector/injector.js:193:19)
at Injector.resolveComponentInstance (../node_modules/#nestjs/core/injector/injector.js:149:33)
at resolveParam (../node_modules/#nestjs/core/injector/injector.js:103:38)
at async Promise.all (index 0)
at Injector.resolveConstructorParams (../node_modules/#nestjs/core/injector/injector.js:118:27)
at Injector.loadInstance (../node_modules/#nestjs/core/injector/injector.js:47:9)
at Injector.loadInjectable (../node_modules/#nestjs/core/injector/injector.js:65:9)
at async Promise.all (index 2)
at InstanceLoader.createInstancesOfInjectables (../node_modules/#nestjs/core/injector/instance-loader.js:62:9)
at ../node_modules/#nestjs/core/injector/instance-loader.js:30:13
● ModalityController › should be defined
expect(received).toBeDefined()
Received: undefined
46 |
47 | it('should be defined', () => {
> 48 | expect(controller).toBeDefined();
| ^
49 | });
50 |
51 | /*
at Object.<anonymous> (modality/modality.controller.spec.ts:48:24)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 5.604 s
I also tried to load the Modality Service and then provide a mock of the Modality Repository:
providers: [
ModalityService,
{
provide: getRepositoryToken(Modality),
useValue: mockRepository,
},
],
But no luck. I don't know how to provide a mocking connection object. What part of the controller is requiring the connection if this is the service job?
Help me guys. What can you see?
EDIT: Adding the controller code:
#ApiTags('Modalidad')
#Controller('modality')
export class ModalityController {
constructor(private modalityService: ModalityService) {}
#ApiOperation({
summary: 'Lista de todas las modalidades, paginados',
})
#OpenApiPaginationResponse(UpdateModalityDto)
#SkipJwtAuth()
#Get()
index(#Query() { items_per_page, page }: PaginationDto) {
return this.modalityService.getAll({
items_per_page: items_per_page || 10,
page: page || 0,
sortOrder: 'ASC',
});
}
#ApiOperation({
summary: 'obtiene una modalidad a partir de una id',
})
#ApiParam({
name: 'id',
type: Number,
})
#SkipJwtAuth()
#OpenApiTransformResponse(UpdateModalityDto)
#UseInterceptors(TransformInterceptor)
#Get(':id')
getOne(#Param('id', ParseIntPipe, EntityPipe()) modality: Modality) {
return modality;
}
#ApiOperation({
summary: 'Crear una nueva modalidad',
})
#SkipJwtAuth()
#OpenApiTransformResponse(UpdateModalityDto, 201)
#UseInterceptors(TransformInterceptor)
#Post()
async store(#Body() modality: CreateModalityDto) {
return await this.modalityService.createOne(modality);
}
#ApiOperation({
summary: 'actualizar una modalidad que ya existe',
})
#ApiParam({
name: 'id',
type: Number,
})
#SkipJwtAuth()
#OpenApiTransformResponse(UpdateModalityDto)
#UseInterceptors(TransformInterceptor)
#InjectItemToBody({ property: 'id' })
#Put(':id')
async update(
#Param('id', ParseIntPipe, EntityPipe()) modality: Modality,
#Body() updatedModality: UpdateModalityDto,
) {
return await this.modalityService.updateOne(modality, updatedModality);
}
}
SOLVED
The problem was that was not detecting the 'Connection' as a valid value. i've change it to the Connection object from the typeorm package
Related
I'm not able to mock chained function of sequelize.
In following example I can mock Query 1, but not Query 2
something.service.ts
// Query 1
await this.table2.findAll<table2>({
attributes: [
'field1'
],
where: {
id: someId
},
});
// Query 2
// returns []
let bill1: any = await this.table2.sequelize.query(`
SELECT
aa.field1,
bg.field2
FROM
table1 aa,
table2 bg
WHERE
bg.id = '${billId}'
AND
aa.id = bg.aggr_id;
`);
something.service.spec.ts
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
{
provide: getModelToken(table2),
useValue: {
// mock successful for query 1
findAll: jest.fn(() => [{}]),
// mock fails for query 2
sequelize: jest.fn().mockReturnValue([]),
query: jest.fn().mockReturnValue([]),
'sequelize.query': jest.fn().mockReturnValue([]),
},
}
],
}).compile();
With this code I'm receiving (for Query 2)
TypeError: this.table2.sequelize.query is not a function
I tried with following code, no luck
sequelize: jest.fn().mockReturnValue([]),
query: jest.fn().mockReturnValue([]),
'sequelize.query': jest.fn().mockReturnValue([]),
sequelize: jest.fn().mockReturnValue({
query: jest.fn(() => [])
})
You can utilize jest.fn().mockReturnThis() to mock the chained function in jest. I have tested this on mocking the TypeORM repository, something like this:
repository.mock.ts
export const mockedRepository = {
find: jest.fn(),
createQueryBuilder: jest.fn(() => ({ // createQueryBuilder contains several chaining methods
innerJoinAndSelect: jest.fn().mockReturnThis(),
getMany: jest.fn(),
})),
};
Somewhere in your service for example:
test.service.ts
//
async findAll(){
return await this.repository
.createQueryBuilder('tableName')
.innerJoinAndSelect('tableName.relation','relation' )
.getMany();
}
//
And finally the unit test spec:
test.service.spec.ts
const module = await Test.createTestingModule({
providers: [
TestService,
{
provide: getRepositoryToken(Test),
useValue: mockedRepository,
}
],
}).compile();
testService =
module.get<TestService>(TestService);
testRepository = module.get<Repository<Test>>(
getRepositoryToken(Test),
);
});
describe('when findAll is called', () => {
beforeEach(() => {
mockedRepository.createQueryBuilder.getMany.mockResolvedValue([]);
});
it('should call innerJoinAndSelect method once', async () => {
await testService.findAll();
expect(mockedRepository.createQueryBuilder.innerJoinAndSelect).toHaveBeenCalledTimes(1);
});
it('should return an empty array', async () => {
expect(await testService.findAll()).toBe([]);
});
});
This is not a real working example but I hope you get the idea.
Issue was with the problem statement itself, this.table.sequelize is an object NOT a function to be chained, following solution worked to mock it.
sequelize: { query: jest.fn(() => []) }
To mock chained functions Farista's solution works.
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
After I gone through the below video for ngrx isolated testing:
John Crowson - Using MockStore in NgRx 8 | AngularUP
I tried to implement the same with my simple project. But I am getting error which I am not able to understand. any one help me to get solved?
it's very big help for me.
test ts file:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { provideMockStore, MockStore } from '#ngrx/store/testing';
import { ShellHomeComponent } from './shell-home.component';
import { StoreOne } from './../../models';
import { Store, select } from '#ngrx/store';
import { cold } from 'jasmine-marbles';
describe('ShellHomeComponent', () => {
let component: ShellHomeComponent;
let fixture: ComponentFixture<ShellHomeComponent>;
let mockStore: MockStore<StoreOne>;
const loadingState = {
loading: true,
items: [{ name: '1' }]
} as StoreOne;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ShellHomeComponent ],
imports: [],
providers: [provideMockStore({initialState: loadingState})]
})
.compileComponents();
mockStore = TestBed.get(Store);
}));
beforeEach(() => {
fixture = TestBed.createComponent(ShellHomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should display loading as true', () => {
const expected = cold('loading', { loading: false, items: [{ name: '3' }] });
expect(component.loading).toBeObservable(expected);
});
});
after run I am getting the following error:
ShellHomeComponent › should display loading as true
expect(received).toEqual(expected) // deep equality
- Expected
+ Received
Array [
Object {
"frame": 0,
"notification": Notification {
- "error": undefined,
- "hasValue": true,
- "kind": "N",
- "value": true,
- },
- },
- Object {
- "frame": 10,
- "notification": Notification {
- "error": undefined,
+ "error": [TypeError: Cannot read property 'loading' of undefined],
"hasValue": false,
- "kind": "C",
+ "kind": "E",
"value": undefined,
},
},
]
41 | it('should display loading as true', () => {
42 | const expected = cold('a|', { a: true });
> 43 | expect(component.loading).toBeObservable(expected);
| ^
44 | });
45 |
46 | });
at compare (node_modules/jasmine-marbles/bundles/jasmine-marbles.umd.js:379:33)
at src/app/module1/shell/shell-home/shell-home.component.spec.ts:43:35
console.warn node_modules/#ngrx/store/bundles/store.umd.js:608
The feature name "storeOne" does not exist in the state, therefore createFeatureSelector cannot access it. Be sure it is imported in a loaded module using StoreModule.forRoot('storeOne', ...) or StoreModule.forFeature('storeOne', ...). If the default state is intended to be undefined, as is the case with router state, this development-only warning message can be ignored.
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 6.321s
I had a similar problem. My tests were failing because state in my reducer was undefined. I was also getting a warning in the console that The feature name "my-feature" does not exist in the state's root, therefore createFeatureSelector cannot access it. Be sure it is imported in a loaded module using StoreModule.forRoot('my-feature', ...) or StoreModule.forFeature('my-feature', ...).
The problem was that I was providing the mock store for the feature when I needed to provide the mock store for the entire app.
Try changing provideMockStore({initialState: loadingState}) to something like provideMockStore<State>({initialState: {shellComponent: loadingState}}) where State is the name of your application's global state (make sure that you import State from your application's state.ts file, not #ngrx/store), and shellComponent is the name of the feature you're testing.
To build off Danny's answer, you will do this:
providers: [
provideMockStore({
initialState: {
'addInvestigationModal': initialState
}
})
]
However, I still had an error An error was thrown in afterAll
error properties: Object({ longStack: 'TypeError: Cannot read property 'property_name' of undefined.
I fixed this by adding
afterEach(() => {
fixture.destroy();
});
I was getting this same warning as #Danny:
The feature name "some-feature" does not exist in the state's root, therefore createFeatureSelector cannot access it. Be sure it is imported in a loaded module using StoreModule.forRoot('some-feature', ...) or StoreModule.forFeature('some-feature', ...).
I forget that I had to add the module into the import list.
const someFeatureModule = StoreModule.forFeature('some-feature', someFeatureReducer);
...
#NgModule({
imports: [
someFeatureModule <-- this was missing
]
...
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
Trying to use angular2-flash-messages and getting the console error _this.flashMessagesService.show is not a function
Followed steps per https://github.com/moff/angular2-flash-messages
app.modules.ts
import { FlashMessagesModule } from 'angular2-flash-messages';
imports: [
BrowserModule,
FormsModule,
FlashMessagesModule.forRoot(),
RouterModule.forRoot(appRoutes)
signin.component.ts has
import { FlashMessagesService } from 'angular2-flash-messages';
signin.component.ts constructor has:
constructor(
private authService: AuthService,
private router: Router,
private flashMessagesService: FlashMessagesService
) { }
onSubmit() {
this.authService.signin(this.email, this.password)
.then((res) => {
this.flashMessagesService.show('You are signed in', {
cssClass: 'alert-success', timeout: 4000
});
this.router.navigate(['/']);
})
.catch((err) => {
this.flashMessagesService.show(err.message, {
cssClass: 'alert-danger', timeout: 4000
});
this.router.navigate(['/signin']);
});
}
and get the error _this.flashMessagesService.show is not a function
// put it inside of onSubmit() method
var flashMessagesService = this.flashMessagesService;
// and call it inside your .then function
// Success
flashMessagesService.show('You are signed in', {
cssClass: 'alert-success', timeout: 4000
});
// Error
flashMessagesService.show(err.message, {
cssClass: 'alert-danger', timeout: 4000
});
Here you can check https://github.com/moff/angular2-flash-messages/issues/35
Add the code
<flash-messages></flash-messages>
in app.component.html file.