Injecting Mocks in NestJS Application for Contract Testing - javascript

Issue
I'm looking for a way to bring up a NestJS application with mocked providers. This is necessary for provider contract tests because a service needs to be brought up in isolation. Using the Pact library, testing the provider assumes that the provider service is already running. It needs to be able to make HTTP requests against the actual server (with some dependencies mocked if necessary). PactJS
Current Research
I've looked into the docs for NestJS and the closest solution I can find is pasted below. From what I can tell, this solution tells the module to replace any provider called CatsService with catsService. This theoretically would work for provider contract testing purposes, but I don't think this allows for the entire app to be brought up, just a module. There is no mention in the docs for being able to bring up the app on a specific port using the testing module. I've tried to call app.listen on the returned app object and it fails to hit a breakpoint placed right after the call.
import * as request from "supertest";
import { Test } from "#nestjs/testing";
import { CatsModule } from "../../src/cats/cats.module";
import { CatsService } from "../../src/cats/cats.service";
import { INestApplication } from "#nestjs/common";
describe("Cats", () => {
let app: INestApplication;
let catsService = { findAll: () => ["test"] };
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [CatsModule]
})
.overrideProvider(CatsService)
.useValue(catsService)
.compile();
app = module.createNestApplication();
await app.init();
});
it(`/GET cats`, () => {
return request(app.getHttpServer())
.get("/cats")
.expect(200)
.expect({
data: catsService.findAll()
});
});
afterAll(async () => {
await app.close();
});
});
Java Example
Using Spring a configuration class, mocks can be injected into the app when running with the "contract-test" profile.
#Profile({"contract-test"})
#Configuration
public class ContractTestConfig {
#Bean
#Primary
public SomeRepository getSomeRepository() {
return mock(SomeRepository.class);
}
#Bean
#Primary
public SomeService getSomeService() {
return mock(SomeService.class);
}
}

Update
Since version 4.4 you can also use listen since it now also returns a Promise.
You have to use the method listenAsync instead of listen so that you can use it with await:
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider(AppService).useValue({ root: () => 'Hello Test!' })
.compile();
app = moduleFixture.createNestApplication();
await app.init();
await app.listenAsync(3000);
^^^^^^^^^^^^^^^^^^^^^
});
Then you can make actual http requests instead of relying on supertest. (I am using the nodejs standard http library in this example.)
import * as http from 'http';
// ...
it('/GET /', done => {
http.get('http://localhost:3000/root', res => {
let data = '';
res.on('data', chunk => data = data + chunk);
res.on('end', () => {
expect(data).toEqual('Hello Test!');
expect(res.statusCode).toBe(200);
done();
});
});
});
Don't forget to close the application or otherwise your test will run until closed manually.
afterAll(() => app.close());

Related

NestJS: Supertest e2e tests skip serializer interceptors

I'm testing an AuthenticationController using supertest. To do so, I am mocking my application using the same configuration than the one I use in my main file main.ts:
// authentication.controller.ts
describe("The AuthenticationController", () => {
let app: INestApplication;
beforeEach(async () => {
userData = {
...mockedUser,
};
const userRepository = {
create: jest.fn().mockResolvedValue(userData),
save: jest.fn().mockReturnValue(Promise.resolve()),
};
const module = await Test.createTestingModule({
controllers: [...],
providers: [...],
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
await app.init();
});
});
This mostly works, but whenever I am testing a controller that should not return a password or an id for example - because of the #Exclude() decorator in an entity definition - the test still returns it to me.
Testing the endpoint manually on Postman still works well.
Does anyone know what could cause that issue?
I just got an answer from one of the developers of NestJS on their official Discord: https://discord.com/invite/nestjs
It turns out the error came from the fact that when mocking the return value of create in my userRepository, I was actually returning an object instead of an instance of a class. Therefore, the following lines had to be replaced:
const userRepository = {
create: jest.fn().mockResolvedValue(userData),
save: jest.fn().mockReturnValue(Promise.resolve()),
};
By the following:
const userRepository = {
create: jest.fn().mockResolvedValue(new User(userData)),
save: jest.fn().mockReturnValue(Promise.resolve()),
};
By simply returning an object, the decorators are not taken into account, so a class instance must be returned.

Jest - mock a named class-export in typescript

I have a node module which exports a few classes, one of which is Client, which I use to create a client (having a few APIs as methods).
I'm trying to test my module which uses this node module as a dependency using Jest. However, I've been unable to successfully mock the one method (say search()) in the Client class.
Here is my spec for myModule:
//index.spec.ts
import * as nock from 'nock';
import * as externalModule from 'node-module-name';
import { createClient } from './../../src/myModule';
describe(() => {
beforeAll(() => {
nock.disableNetConnect();
});
it('test search method in my module', () => {
jest.mock('node-module-name');
const mockedClient = <jest.Mock<externalModule.Client>>externalModule.Client;
const myClient = createClient({/*params*/}); //returns instance of Client class present in node module by executing Client() constructor
myClient.searchByName('abc'); //calls search API - I need to track calls to this API
expect(mockedClient).toHaveBeenCalled();
expect(mockedClient.prototype.search).toHaveBeenCalledWith('abc');
});
});
This, however, doesn't create a mock at all and triggers a nock error since the search API tries to connect to the url (given through params).
I've also tried mocking the Client class like the following. While successfully creating a mock for the Client class and also the search API (verified that search() is also mocked through console logs), it gives me an error while I try to check if search() has been called.
externalModule.Client = jest.fn(() => { return { search: jest.fn(() => Promise.resolve('some response')) } });
//creates the mock successfully, but not sure how to track calls to 'search' property
const client = myModule.createClient(/*params*/);
client.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled(); //Successful
expect(externalModule.Client.prototype.search).toHaveBeenCalled(); //returns error saying "jest.fn() value must be a mock function or spy, Received: undefined"
I'm not sure what I'm doing wrong. Thank you in advance.
Mocking whole module
Try moving jest.mock to the top of file
//index.spec.ts
const search = jest.fn();
jest.mock('node-module-name', () => ({
Client: jest.fn(() => ({ search }))
}));
import * as nock from 'nock';
import * as externalModule from 'node-module-name';
import { createClient } from './../../src/myModule';
describe(() => {
beforeAll(() => {
nock.disableNetConnect();
});
it('test search method in my module', () => {
const myClient = createClient({/*params*/});
myClient.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled();
expect(search).toHaveBeenCalledWith('abc');
externalModule.Client.mockClear();
search.mockClear();
});
});
Mocking only Client
Create search constant and track it.
const search = jest.fn();
externalModule.Client = jest.fn(() => ({ search }));
const client = myModule.createClient(/*params*/);
client.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled();
expect(search).toHaveBeenCalled();
Here is how I mocked it. I had to change naming and removing some code to avoid exposing original source.
jest.mock('../foo-client', () => {
return { FooClient: () => ({ post: mockPost }) }
})
Full code.
// foo-client.ts
export class FooClient {
constructor(private config: any)
post() {}
}
// foo-service.ts
import { FooClient } from './foo-client'
export class FooLabelService {
private client: FooClient
constructor() {
this.client = new FooClient()
}
createPost() {
return this.client.post()
}
}
// foo-service-test.ts
import { FooService } from '../foo-service'
const mockPost = jest.fn()
jest.mock('../foo-client', () => {
return { FooClient: () => ({ post: mockPost }) }
})
describe('FooService', () => {
let fooService: FooService
beforeEach(() => {
jest.resetAllMocks()
fooService = new FooService()
})
it('something should happened', () => {
mockPost.mockResolvedValue()
fooService.createPost()
})
})

Use Jest to mock external user module in an imported module

I don't know if I'm missing something in the docs, but I have this situation:
// test.js
import User from './user'
it("should load initial data", async() => {
const users = new User()
const user = await users.load()
})
// User.js
import Api from './api'
export default class User {
async load() {
const res = await Api.fetch() // prevent/mock this in testing
}
}
What is the Jest-way to prevent/mock the external Api module in User.js. I do not want User.js to make a real network request within the test.
Further to this, I'm looking for a more generic mocking solution, ie. say I'm testing in React Native, and I want to mock NativeModules.SettingsManager.settings.AppleLocale, for example. Lets say Api.fetch() calls the line above, and doesn't make a HTTP request
spyOn in combination with mock functions like mockImplementation will provide what you are looking for.
Here is a working example:
// ---- api.js ----
export const getData = () => {
return Promise.resolve('hi');
}
// ---- user.js ----
import { getData } from './api'
export default class User {
async load() {
return await getData(); // mock this call in user.test.js
}
}
// ---- user.test.js ----
import User from './user'
import * as Api from './api'; // import * so we can mock 'getData' on Api object
describe('User', () => {
it('should load initial data', async() => {
const mock = jest.spyOn(Api, 'getData'); // create a spy
mock.mockImplementation(() => Promise.resolve('hello')); // give it a mock implementation
const user = new User();
const result = await user.load();
expect(result).toBe('hello'); // SUCCESS, mock implementation called
mock.mockRestore(); // restore original implementation when we are done
});
});
If you need to mock responses to HTTP requests, then you should check out nock. It has a clean API that allows a lot of flexibility in creating HTTP responses to specific requests.

Angular 2 mock Http get() to return local json file

What is the easiest way to mock the response returned by Http get() in Angular 2?
I have local data.json file in my working directory, and I want get() to return response containing that data as a payload, simulating the rest api.
Documents for configuring the Backend object for Http seemed somewhat obscure and overcomplicated for such a simple task.
You need to override the XhrBackend provider with the MockBackend one. You need then to create another injector to be able to execute a true HTTP request.
Here is a sample:
beforeEachProviders(() => {
return [
HTTP_PROVIDERS,
provide(XHRBackend, { useClass: MockBackend }),
SomeHttpService
];
});
it('Should something', inject([XHRBackend, SomeHttpService], (mockBackend, httpService) => {
mockBackend.connections.subscribe(
(connection: MockConnection) => {
var injector = ReflectiveInjector.resolveAndCreate([
HTTP_PROVIDERS
]);
var http = injector.get(Http);
http.get('data.json').map(res => res.json()).subscribe(data) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: data
})));
});
});
}));
By the way, you need to mock the XHRBackend and provide mocked data in a class with the createDb method. createDb method returns the mocked JSON object. To load that data provide correct URL to http.get, for example, if JSON object is contained in a variable mockedObject, then the URL should be "app\mockedObject".
You can read more details here: https://angular.io/docs/ts/latest/guide/server-communication.html.
You can use the HttpTestingController available via the core TestBed as to me it feels more intuitive (each to their own, of course). Untested snippet:
import { TestBed, async } from '#angular/core/testing';
import { HttpTestingController } from '#angular/common/http/testing';
import { MyApiService } from './my-api.service';
export function main() {
describe('Test set', () => {
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
providers: [MyApiService]
});
httpMock = TestBed.get(HttpTestingController);
});
it('should call get', async(() => {
const data: any = {mydata: 'test'};
let actualResponse: any = null;
MyApiService.get().subscribe((response: any) => {
actualResponse = response;
});
httpMock.expectOne('localhost:5555/my-path').flush(data);
expect(actualResponse).toEqual(data);
}));
});
}

How do you substitute HttpClient in Aurelia?

I'm brand new to Aurelia.
How would you change the following code to provide a dummy HttpClient, e.g. a json reader instead that would provide just a static set of json data, negating the need for a server in development.
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
#inject(HttpClient)
export class Users {
heading = 'Github Users';
users = [];
constructor(http) {
http.configure(config => {
config
.useStandardConfiguration()
.withBaseUrl('https://api.github.com/');
});
this.http = http;
}
activate() {
return this.http.fetch('users')
.then(response => response.json())
.then(users => this.users = users);
}
}
There's a couple steps required to get the demo code in your original post to a state where we can substitute HttpClient implementations.
Step 1
Remove the configuration code in the class's constructor...
These lines:
users.js
...
http.configure(config => {
config
.useStandardConfiguration()
.withBaseUrl('https://api.github.com/');
});
...
Should move to the main.js file:
main.js
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging();
configureContainer(aurelia.container); // <--------
aurelia.start().then(a => a.setRoot());
}
function configureContainer(container) {
let http = new HttpClient();
http.configure(config => {
config
.useStandardConfiguration()
.withBaseUrl('https://api.github.com/');
});
container.registerInstance(HttpClient, http); // <---- this line ensures everyone that `#inject`s a `HttpClient` instance will get the instance we configured above.
}
Now our users.js file should look like this:
users.js
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
#inject(HttpClient)
export class Users {
heading = 'Github Users';
users = [];
constructor(http) {
this.http = http;
}
activate() {
return this.http.fetch('users')
.then(response => response.json())
.then(users => this.users = users);
}
}
Step 2:
Mock the HttpClient.
The user.js module only uses the fetch method which returns a Response object that has a json method. Here's a simple mock:
let mockUsers = [...todo: create mock user data...];
let httpMock = {
fetch: url => Promise.resolve({
json: () => mockUsers
})
};
Step 3:
Reconfigure the container to use the http mock:
In step 1 we added a configureContainer function to the main.js module that registered a configured HttpClient instance in the container. If we wanted to use our mock version the configureContainer function would change to this:
main.js
...
let mockUsers = [...todo: create mock user data...];
let httpMock = {
fetch: url => Promise.resolve({
json: () => mockUsers
})
};
function configureContainer(container) {
container.registerInstance(HttpClient, httpMock);
}
More info on configuring the container here: https://github.com/aurelia/dependency-injection/issues/73
There is another possibility to provide static data for the application during development. Navigation Skeleton already comes with Gulp and BrowserSync, so we used those to fake API calls.
Let's say you load JSON data from /api virtual directory, so e.g.
GET /api/products
In this case your just need two things to fake it.
Put your mock data into files
Go to the root folder of your Aurelia app and create an /api folder.
Create a /api/products subfolder and put a new file called GET.json. This file should contain the JSON, e.g.
GET.json
[ { "id": 1, "name": "Keyboard", "price": "60$" },
{ "id": 2, "name": "Mouse", "price": "20$" },
{ "id": 3, "name": "Headphones", "price": "80$" }
]
Configure BrowserSync to mock your API calls
Navigate to /build/tasks folder and edit the serve.js file. Change the definition of serve task to the following code:
gulp.task('serve', ['build'], function(done) {
browserSync({
online: false,
open: false,
port: 9000,
server: {
baseDir: ['.'],
middleware: function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
// Mock API calls
if (req.url.indexOf('/api/') > -1) {
console.log('[serve] responding ' + req.method + ' ' + req.originalUrl);
var jsonResponseUri = req._parsedUrl.pathname + '/' + req.method + '.json';
// Require file for logging purpose, if not found require will
// throw an exception and middleware will cancel the retrieve action
var jsonResponse = require('../..' + jsonResponseUri);
// Replace the original call with retrieving json file as reply
req.url = jsonResponseUri;
req.method = 'GET';
}
next();
}
}
}, done);
});
Now, when your run gulp serve, BrowserSync will be handling your API calls and serving them from the static files on disk.
You can see an example in my github repo and more description in my Mocking API calls in Aurelia.

Categories

Resources