I'm using NestJS 7.6.0 and I'm experiencing a problem with E2E testing.
I have a very simple module that exposes a /health route in its controller. I'd like to test this route using the E2E testing feature of nestJs documented here:
https://docs.nestjs.com/fundamentals/testing#end-to-end-testing
I'm currently using the quite same code:
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule]
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/health (GET)', async() => {
return await request(app.getHttpServer())
.get('/health')
.expect(200)
});
});
If I run this test with npm run test I have this error:
npm run test
> dv-ewok-api#0.0.1 test ....
> jest
[Nest] 54638 - 28/06/2021, 14:53:30 [ExceptionHandler] Health Check has failed! {"ready":{"status":"down","message":"connect ECONNREFUSED 127.0.0.1:3000"}}
FAIL src/health/health.controller.spec.ts
● AppController (e2e) › /health (GET)
expected 200 "OK", got 503 "Service Unavailable"
32 | return await request(app.getHttpServer())
33 | .get('/health')
> 34 | .expect(200)
| ^
35 |
36 | });
37 | });
It seems that the httpServer is not running.
To make it work I have to modify the code like this:
...
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule]
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
await app.listen(3000); // <===== ADD THIS LINE
});
...
I just added a app.listen(3000) and the test passed. I don't know if this is an oversight in the documentation or if I did it wrong.
Thank you for your help.
Edit for adding some more details
For the HealthCheck part, I'am using the NestJS Terminus integration described here.
So, my app.module looks like this :
import { Module } from '#nestjs/common';
import { TerminusModule } from '#nestjs/terminus';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { HealthController } from './health/health.controller';
#Module({
imports: [TerminusModule],
controllers: [AppController, HealthController],
providers: [AppService],
})
export class AppModule {}
As you can see, I import the TerminusModule and I declare my 2 controllers: AppController and HealthController.
In AppController I have just a /ping route that says "hello world"
import { Controller, Get } from '#nestjs/common';
import { AppService } from './app.service';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get('/ping')
getHello(): string {
return this.appService.getHello();
}
}
And in HealthController I have just the /health route that calls the /ping on localhost.
import { Controller, Get } from "#nestjs/common";
import { HealthCheck, HealthCheckService, HttpHealthIndicator } from "#nestjs/terminus";
#Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private http: HttpHealthIndicator,
) {}
#Get()
#HealthCheck()
check() {
return this.health.check([
() => this.http.pingCheck('ping', 'http://localhost:3000/ping'),
]);
}
}
And if you run tests with npm run test, it shows the 503 error linked above. To make it work, I need to add await app.listen(3000); in the beforeAll() method right after the init() (cf. above).
I think my problem comes from "terminus" because if I start a new NestJS project which does not use Terminus, e2e testing works fine, without the need to add the .listen().
Do I need to do something special in the createTestingModule() with TerminusModule?
Thank you
Related
I'm trying to make unit test with nestjs and objection. The problem I have is that I can't mock the "User" Model that is injected with the decorator "#InjectModel". I searched a lot to find a solution but I didn't find anything.
users.service.ts
import { HttpException, HttpStatus, Inject, Injectable } from '#nestjs/common';
import { CreateUserDto } from './create-user.dto';
import { User } from 'src/app.models';
import { InjectModel } from 'nestjs-objection';
#Injectable()
export class UsersService {
constructor(
#InjectModel(User) private readonly userModel: typeof User,
) {}
async create(createUserDto: CreateUserDto) {
try {
const users = await this.userModel.query().insert(createUserDto);
return users
} catch (err) {
console.log(err)
throw new HttpException(err, HttpStatus.BAD_REQUEST);
}
}
}
users.service.spec.ts
import { Test, TestingModule } from "#nestjs/testing";
import { UsersService } from "../src/users/users.service";
import { CreateUserDto } from "src/users/create-user.dto";
import { User } from "../src/app.models";
import { getObjectionModelToken } from 'nestjs-objection';
describe('userService', () => {
let userService: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: User,
useValue: {}
},
],
}).compile();
userService = module.get<UsersService>(UsersService);
});
it('Should be defined', () => {
expect(userService).toBeDefined();
});
it('Should add pin to a created user', async () => {
const createUserDTO: CreateUserDto = {
email: 'mockEmail#mock.com',
userName: 'user'
}
const res = await userService.create(createUserDTO)
expect(res).toHaveProperty('pin')
});
I tried to use import { getObjectionModelToken } from 'nestjs-objection'; inside provider like this:
providers: [
UsersService,
{
provide: getObjectionModelToken(User),
useValue: {}
},
],
I got this error
It asks for a "connection" but I don't know what to put on it.
I suppose "getObjectionModelToken" is the function to mock the "InjectModel". When I pass an empty string
I got this error:
● Test suite failed to run
Cannot find module 'src/app.models' from '../src/users/users.service.ts'
Require stack:
C:/nestjs-project/nestjs-knex/src/users/users.service.ts
users.repository.spec.ts
1 | import { HttpException, HttpStatus, Inject, Injectable } from '#nestjs/common';
2 | import { CreateUserDto } from './create-user.dto';
> 3 | import { User } from 'src/app.models';
| ^
4 | import {
5 | InjectModel,
6 | synchronize,
at Resolver._throwModNotFoundError (../node_modules/jest-resolve/build/resolver.js:491:11)
at Object.<anonymous> (../src/users/users.service.ts:3:1)
If I change the path it breaks the correct functionality of the app
That looks like an error from jest not understanding what src/* imports are. Either use relative imports rather than absolute (e.g. use import { User } from '../app.models') or tell jest how to resolve src/* imports via the moduleNameMapper in your jest.config.js or package.json
{
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/path/to/src/$1"
}
}
I think based on the error your /path/to/src should be ../src but I'm not 100% sure, so make sure you set that correctly.
I'm trying to unit test a very basic service that uses ngx-socket-io using Jasmine + Karma.
I keep getting Error: Cannot call setTimeout from within a sync test.
Here's my service:
import { Injectable } from '#angular/core';
import { Socket } from 'ngx-socket-io';
import { Message } from 'src/app/models/message';
#Injectable({
providedIn: 'root'
})
export class MessageService {
constructor(private socket: Socket) { };
newMessages(){
return this.socket.fromEvent<Message>('newMessage');
}
newMessage(message: String) {
this.socket.emit('messageSent', message);
}
}
This is my unit test:
import { TestBed } from '#angular/core/testing';
import { MessageService } from './message.service';
import SocketMock from 'socket.io-mock';
import { WrappedSocket } from 'ngx-socket-io/src/socket-io.service';
describe('MessageService', () => {
let service: MessageService;
let socket: WrappedSocket = new WrappedSocket(new SocketMock());
beforeEach(() => {
TestBed.configureTestingModule({
providers :[
MessageService,
{provide: WrappedSocket, useValue: socket}
]
});
service = TestBed.get(MessageService);
});
it('should be created', async () => {
expect(service).toBeTruthy();
});
});
Here is the full error output:
Error: Cannot call setTimeout from within a sync test.
at SyncTestZoneSpec.onScheduleTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:366:1)
at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.scheduleTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:404:1)
at Zone../node_modules/zone.js/dist/zone.js.Zone.scheduleTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:238:1)
at Zone../node_modules/zone.js/dist/zone.js.Zone.scheduleMacroTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:261:1)
at scheduleMacroTaskWithCurrentZone (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:1245:1)
at http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:2317:1
at proto.<computed> (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:1569:1)
at Request.create (http://localhost:9876/_karma_webpack_/webpack:/node_modules/engine.io-client/lib/transports/polling-xhr.js:268:1)
at new Request (http://localhost:9876/_karma_webpack_/webpack:/node_modules/engine.io-client/lib/transports/polling-xhr.js:165:1)
at XHR.request (http://localhost:9876/_karma_webpack_/webpack:/node_modules/engine.io-client/lib/transports/polling-xhr.js:92:1)
I just want to test that the service is created for now. What's going on, and how can I get the test to pass?
Try to use an async beforeEach. Probably setTimeout is called from there.
Check your test.ts file. Mainly the zone.js imports
Order should be
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';
Reference - https://github.com/angular/zone.js/issues/1058
I want to run an in-memory database for my tests, but I'm unable to make my application connect to it when I run npm test.
When I run npm test i get:
Connection fails: Error: ER_ACCESS_DENIED_ERROR: Access denied for user ''#'172.21.0.1' (using password: NO)
This is happening because I'm not setting any env variables on npm test, but I don't want to use MySQL on my tests and just an in-memory database, here what I have.
testdb.datasources.ts
import {juggler} from '#loopback/repository';
export const testdb: juggler.DataSource = new juggler.DataSource({
name: 'testdb',
connector: 'memory',
});
country.controller.acceptance.ts
import {Client, expect} from '#loopback/testlab';
import {MyApplication} from '../..';
import {setupApplication} from './test-helper';
import {givenEmptyDatabase} from '../helpers/database.helpers';
describe('Country Controller', () => {
let app: MyApplication;
let client: Client;
before('setupApllication', async () => {
({app, client} = await setupApplication());
});
before(givenEmptyDatabase);
// before(givenRunningApp);
after(async () => {
await app.stop();
});
it('Should count 0 countries', async () => {
const res = await client.get('/countries/count').expect(200);
//assertations
expect(res.body.count).to.equal(0);
});
});
test-helper.ts
import {MyApplication} from '../..';
import {
createRestAppClient,
givenHttpServerConfig,
Client,
} from '#loopback/testlab';
import {testdb} from '../fixtures/datasources/testdb.datasource';
export async function setupApplication(): Promise<AppWithClient> {
const app = new MyApplication({
rest: givenHttpServerConfig({host: 'localhost'}),
});
app.dataSource(testdb); // <--- Hoped this would do the job
await app.boot();
await app.start();
const client = createRestAppClient(app);
return {app, client};
}
export interface AppWithClient {
app: MyApplication;
client: Client;
}
The Country controller is just a standard controller create using lb4 controller.
#get('/countries/count', {
responses: {
'200': {
description: 'country model count',
content: {'application/json': {schema: countschema}},
},
},
})
async count(
#param.query.object('where', getwhereschemafor(country)) where?: where,
): promise<count> {
return await this.countryrepository.count(where);
}
Any ideas on whats going wrong?
i Found this way that works for me
Change this:
app.dataSource(testdb);
To this:
await app.bind('datasource.config.db').to({
name: 'db',
connector: 'memory'
});
You are just changing the configurations of datasource to use a local database but is still the same datasource!
make sure that the string of bind is the same used in your main datasource!
As Angular team is constantly upgrading/deprecating stuff in Angular 2 RC versions I encountered this problem.
I have a component that has a Dependency Injection (DI), which is actually a service (UserService in this case). This UserService of course has some DIs of its own. After updating to the latest RC4 of Angular 2 I realised that I cannot create similar tests any more.
So as the docs are not mentioning something relative here's my code (simplified for this question).
My component:
import { Component } from '#angular/core';
import { MdButton } from '#angular2-material/button';
import {
MdIcon,
MdIconRegistry
} from '#angular2-material/icon';
import { UserService } from '../../services/index';
#Component({
moduleId: module.id,
selector: 'logout-button',
templateUrl: 'logout-button.component.html',
styleUrls: ['logout-button.component.css'],
providers: [MdIconRegistry, UserService],
directives: [MdButton, MdIcon]
})
export class LogoutButtonComponent {
constructor(public userService: UserService) {}
/**
* Call UserService and logout() method
*/
logout() {
this.userService.logout();
}
}
Component's DI, UserService whic as you can see has some DIs (Router, AuthHttp & Http):
import { Injectable } from '#angular/core';
import {
Http,
Headers
} from '#angular/http';
import {
AuthHttp,
JwtHelper
} from 'angular2-jwt';
import { Router } from '#angular/router';
import { UMS } from '../common/index';
#Injectable()
export class UserService {
constructor(
private router: Router,
private authHttp: AuthHttp,
private http: Http) {
this.router = router;
this.authHttp = authHttp;
this.http = http;
}
/**
* Logs out user
*/
public logout() {
this.authHttp.get(UMS.url + UMS.apis.logout)
.subscribe(
data => this.logoutSuccess(),
err => this.logoutSuccess()
);
}
}
And here's the test for the component:
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
fakeAsync,
TestComponentBuilder
} from '#angular/core/testing';
import { AuthHttp } from 'angular2-jwt';
import { Router } from '#angular/router';
import { Http } from '#angular/http';
import { LogoutButtonComponent } from './logout-button.component';
import { UserService } from '../../services/index';
describe('Component: LogoutButtonComponent', () => {
beforeEachProviders(() => [
LogoutButtonComponent,
UserService
]);
it('should inject UserService', inject([LogoutButtonComponent],
(component: LogoutButtonComponent) => {
expect(component).toBeTruthy();
}));
});
Don't worry about the (it) for now.
As you can see I;m adding the related providers on the beforeEachProviders.
In this case I'm getting an error when I run the tests:
Error: No provider for Router! (LogoutButtonComponent -> UserService -> Router)
Which is expected let's say.
So in order to don't get those errors I'm adding the service's DIs in the providers also:
beforeEachProviders(() => [
LogoutButtonComponent,
Router,
AuthHttp,
Http,
UserService
]);
But now I'm, getting this error:
Error: Cannot resolve all parameters for 'Router'(?, ?, ?, ?, ?, ?, ?). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'Router' is decorated with Injectable.
I'm really trying to figure out what's happening so I found some related answers here but ALL are outdated and covers the old router-deprecated or angular2/router but both are deprecated and are not covering this case.
Would love some help on this and maybe some resources as I cannot find anything related to the latest version of Router: "#angular/router": "3.0.0-beta.2", and RC4.
Thanks
UPDATE!
I manage to bypass the two errors above and now I can access the component. Here's the description code:
describe('Component: LogoutButtonComponent', () => {
let component: LogoutButtonComponent;
let router: any = Router;
let authHttp: any = AuthHttp;
let http: any = Http;
let service: any = new UserService(router, authHttp, http);
beforeEachProviders(() => [
LogoutButtonComponent
]);
beforeEach(() => {
component = new LogoutButtonComponent(service);
});
it('should inject UserService', () => {
expect(component.userService).toBeTruthy();
});
it('should logout user', () => {
localStorage.setItem('token', 'FOO');
component.logout();
expect(localStorage.getItem('token')).toBeUndefined();
});
});
But it seems that even that the DI service is injected and accessible the DIs of the service are not. So now I get this error:
TypeError: this.authHttp.get is not a function
Any ideas?
It looks like you were experiencing a dependencies loop problem, because your UserSerivce also need inject AuthHttp, Http, etc... it really will be disturb once if you need test your component.
My way is just create a mock UserSerivce and return the expect mocked value through UserService.logout() method, because you don't have to know what really happened in UserService, all you need is just a return value:
let MockUserService = {
logout() {
// return some value you need
}
}
Then, in test suite:
import { provide } from '#angular/core'
beforeEachProviders(() => [
provide(UserService, {useClass: MockUserService})
])
... detail test code here
I hope this works for you.
And here is a post that helps me a lot:
https://developers.livechatinc.com/blog/testing-angular-2-apps-dependency-injection-and-components/
With RC4, some workarounds are needed to use http in a test. See also this issue (should be fixed in RC5):
https://github.com/angular/angular/issues/9294
Adding this to my unit-tests.html fixed it for me:
System.import('#angular/platform-browser/src/browser/browser_adapter').then(function(browser_adapter) {
browser_adapter.BrowserDomAdapter.makeCurrent();
})
I use Angular2 RC1 and I have several unit tests regarding different components with the following structure:
import {provide} from '#angular/core';
import {
TestComponentBuilder
} from '#angular/compiler/testing';
import {
beforeEach,
ddescribe,
xdescribe,
describe,
expect,
iit,
inject,
injectAsync,
async,
beforeEachProviders,
setBaseTestProviders,
it,
xit
} from '#angular/core/testing';
import {
TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS
} from '#angular/platform-browser-dynamic/testing/browser';
describe('Test component 1', () => {
setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
it('should something',
async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
var updateService = new UpdateService();
tcb.overrideProviders(ShapeCircleLayerComponent, [
provide(UpdateService, { useValue: updateService })
])
.createAsync(Component1).then((componentFixture) => {
(...)
});
});
});
});
Each test works if run alone but when I run them at the same time within Karma, I get the following error:
Chrome 50.0.2661 (Linux 0.0.0) Test for shape circle layer encountered a declaration exception FAILED
Error: Cannot set /home/(...)/my-project providers because it has already been called
at new BaseException (/home/(...)/my-project/node_modules/#angular/core/src/facade/exceptions.js:17:23)
at Object.setBaseTestProviders (/home/(...)/my-project/node_modules/#angular/core/testing/test_injector.js:74:15)
```
It seems that several tests that set base test providers (TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS) can't be executed at the same time.
Does anyone have this problem? Thanks very much!
As #teleaziz suggested, you should do this only once. So such processing needs to be moved into the karma-test-shim.js file. Here is a sample:
System.import('#angular/platform-browser/src/browser/browser_adapter')
.then(function(browser_adapter) { browser_adapter.BrowserDomAdapter.makeCurrent(); })
.then(function() {
return Promise.all([
System.import('#angular/core/testing'),
System.import('#angular/platform-browser-dynamic/testing/browser')
]);
})
.then(function(modules) {
var testing = modules[0];
var testingBrowser = modules[1];
testing.setBaseTestProviders(
testingBrowser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
testingBrowser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
})
.then(function() { return Promise.all(resolveTestFiles()); })
.then(function() { __karma__.start(); }, function(error) { __karma__.error(error.stack || error); });