Start and stop server with supertest - javascript

I have the following server class :
import express, { Request, Response } from 'express';
export default class Server {
server: any;
exp: any;
constructor() {
this.exp = express();
this.exp.get('/', (_req: Request, res: Response) => {
res.json('works');
});
}
start(): void {
this.server = this.exp.listen(3000);
}
stop(): void {
this.server.close();
}
}
I'm using supertest for end-to-end testing. I wish to start my application beforeAll tests and stop it when the tests are done.
It's easy to do that using beforAll and afterAll where I can just once instanciate the Server class and call the start and close methods.
But as I have 10+ controllers to test, I want to avoid to start and stop the server during each test file.
I found on the documentation the setupFiles and setupFilesAfterEnv but I can't stop the server since the instance is not "shared" in the two files.
This is an example of 1 test file :
import supertest from 'supertest';
describe('Album Test', () => {
let app: App;
beforeAll(async (done) => {
app = new App();
await app.setUp(); // database connection (not mentionned in the preivous example)
done();
});
afterAll(async (done) => {
await app.close();
app.server.stop();
done();
});
const api = supertest('http://localhost:3000');
it('Hello API Request', async () => {
const result = await api.get('/v1/user');
expect(result.status).toEqual(200);
...
});
});
This works totally fine but I'm duplicating this beforeAll and afterAll methods in every test file. Is there a way to declare it only once ?
Thanks

Try this it works
const supertest = require('supertest')
const app = require('../../app')
describe('int::app', function(){
let request = null
let server = null
before(function(done){
server = app.listen(done)
request = supertest.agent(server)
})
after(function(done){
server.close(done)
})
it('should get /api/v1/laps/85299', function(){
return request.get('/api/v1/laps/85299')
.expect(200, { data: {} })
})
})

You could use setupFiles to set up test fixtures globally. You can assign variables that you want to use in multiple test files to Node.js global object.
E.g.
app.ts:
import express, { Request, Response } from 'express';
export default class Server {
server: any;
exp: any;
constructor() {
this.exp = express();
this.exp.get('/', (_req: Request, res: Response) => {
res.json('works');
});
}
start(): void {
this.server = this.exp.listen(3000);
}
stop(): void {
this.server.close();
}
}
app.setup.js:
const App = require('./app').default;
beforeAll(() => {
global.app = new App();
global.app.exp.set('test setup', 1);
console.log('app setup');
});
afterAll(() => {
console.log('app stop');
});
jest.config.js:
module.exports = {
preset: 'ts-jest/presets/js-with-ts',
testEnvironment: 'node',
setupFilesAfterEnv: [
'./jest.setup.js',
'/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/61659975/app.setup.js',
],
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
verbose: true,
};
a.controller.test.js:
describe('controller a', () => {
it('should pass', () => {
console.log('test setup:', global.app.exp.get('test setup'));
expect(1 + 1).toBe(2);
});
});
b.controller.test.js:
describe('controller b', () => {
it('should pass', () => {
console.log('test setup:', global.app.exp.get('test setup'));
expect(1 + 1).toBe(2);
});
});
unit test results:
PASS stackoverflow/61659975/a.controller.test.js
controller a
✓ should pass (5ms)
console.log
app setup
at Object.<anonymous> (stackoverflow/61659975/app.setup.js:6:11)
console.log
app setup
at Object.<anonymous> (stackoverflow/61659975/app.setup.js:6:11)
console.log
test setup: 1
at Object.<anonymous> (stackoverflow/61659975/b.controller.test.js:3:13)
console.log
test setup: 1
at Object.<anonymous> (stackoverflow/61659975/a.controller.test.js:3:13)
console.log
app stop
at Object.<anonymous> (stackoverflow/61659975/app.setup.js:10:11)
console.log
app stop
at Object.<anonymous> (stackoverflow/61659975/app.setup.js:10:11)
PASS stackoverflow/61659975/b.controller.test.js
controller b
✓ should pass (3ms)
Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 6.749s, estimated 12s

Related

Mocking es6 class static methods with Jest

I am really having big trouble handling this issue. I have read Jest docs a lot, also other articles, but nothing has helped me yet, even chatGPT.
I am trying to test this class.
export class CookiesManager extends Auth {
static get(key: string) {
return this.cookies.get(key);
}
static set(key: string, value: any, config?: CookieSetOptions) {
this.cookies.set(key, value, config || this.config);
}
static remove(key: string) {
const { hostname } = new URL(`${process.env.WEB_HOST}`);
this.cookies.remove(key, { ...this.config, domain: hostname });
}
}
Here is the Auth class, but in my case, I haven't even reached here or I guess I will not need to handle that part in the scope of this one. Anyway, just for a reference.
import moment from 'moment';
import Cookies, { CookieSetOptions } from 'universal-cookie';
export class Auth {
static cookies = new Cookies();
static config: CookieSetOptions = {
path: '/',
maxAge: 1800,
expires: moment().add(30, 'm').toDate(),
secure: process.env.NODE_ENV !== 'development',
};
static setToken(token: string) {
token && this.cookies.set('auth_token', token, this.config);
}
static getToken() {
return this.cookies.get('auth_token');
}
static clear() {
this.cookies.remove('auth_token', this.config);
}
}
I have written the mock for the CookiesManager class.The module has also other named exports.
jest.mock('core/utils/storage-manager.ts', () => {
const originalClasses = jest.requireActual('core/utils/storage-manager.ts');
class CookiesManagerMock {
static config = {
path: '/',
maxAge: 1800,
}
static set = jest.fn().mockImplementation((key, value, config) => {
console.log('Mock set called');
mockCookies[key] = value;
})
static get = jest.fn().mockImplementation((key) => {
console.log('Mock get called');
return mockCookies[key];
})
static remove = jest.fn().mockImplementation((key, config) => {
console.log('Mock remove called');
delete mockCookies[key];
})
}
return {
...originalClasses,
CookiesManager: CookiesManagerMock,
}
})
This is the test block.
describe('CookiesManager', () => {
afterEach(() => {
jest.restoreAllMocks();
});
test('It should set a cookie', () => {
CookiesManager.set('test_key', 'test_value');
console.log(mockCookies.test_key, 'mockCookies')
expect(CookiesManager.set).toHaveBeenCalledWith('test_key', 'test_value');
});
test('It should get a cookie', () => {
CookiesManager.get.mockReturnValueOnce('test_value');
expect(CookiesManager.get('test_key')).toEqual('test_value');
expect(CookiesManager.get).toHaveBeenCalledWith('test_key');
});
test('It should remove a cookie', () => {
CookiesManager.remove('test_key');
expect(CookiesManager.remove).toHaveBeenCalledWith('test_key');
});
})
UPDATED
Even though the tests are passing, non of the console.log statements are being called in mocked class. And also mockCookies is always empty. I have tried the same with CommonJS modules and the strange thing is that console.log statements are being called.
In Jest docs, it's clearly stated.
mockFn.mockImplementation(fn)
Accepts a function that should be used as the implementation of the
mock. The mock itself will still record all calls that go into and
instances that come from itself – the only difference is that the
implementation will also be executed when the mock is called.
Maybe I am getting something wrong and don't understand the nature of the mocks.
You didn't mock the static methods correctly. The way you are using is trying to mock the instance methods of a class. Here is a solution, create a mock CookiesManager class with mock static methods.
storage-manager.ts:
type CookieSetOptions = any;
export class CookiesManager {
static get(key: string) {}
static set(key: string, value: any, config?: CookieSetOptions) {}
static remove(key: string) {}
}
export const a = () => 'a'
export const b = () => 'b'
storage-manager.test.ts:
import { CookiesManager, a, b } from './storage-manager';
jest.mock('./storage-manager', () => {
const originalModules = jest.requireActual('./storage-manager');
const mockCookies = {};
class CookiesManagerMock {
static set = jest.fn().mockImplementation((key, value, config) => {
console.log('Mock set called');
mockCookies[key] = value;
});
static get = jest.fn().mockImplementation((key) => {
console.log('Mock get called');
return mockCookies[key];
});
static remove = jest.fn().mockImplementation((key, config) => {
console.log('Mock remove called');
delete mockCookies[key];
});
}
return {
...originalModules,
CookiesManager: CookiesManagerMock,
};
});
describe('75323871', () => {
test('It should set a cookie', () => {
const config = { path: '/', maxAge: 1800 };
expect(jest.isMockFunction(CookiesManager.set)).toBeTruthy();
CookiesManager.set('test_key', 'test_value', config);
expect(CookiesManager.set).toHaveBeenCalledWith('test_key', 'test_value', config);
expect(CookiesManager.get('test_key')).toBe('test_value');
// other named exports
expect(a()).toBe('a');
expect(b()).toBe('b');
});
});
jest.config.js:
module.exports = {
preset: 'ts-jest/presets/js-with-ts'
};
Test result:
PASS stackoverflow/75323871/storage-manager.test.ts (8.614 s)
75323871
✓ It should set a cookie (17 ms)
console.log
Mock set called
at Function.<anonymous> (stackoverflow/75323871/storage-manager.test.ts:9:15)
console.log
Mock get called
at Function.<anonymous> (stackoverflow/75323871/storage-manager.test.ts:14:15)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.358 s, estimated 10 s
Note: Maybe jest.spyOn(CookiesManager, 'set').mockImplementation() is a better solution, you only need to mock the specific static method. see https://jestjs.io/docs/es6-class-mocks#static-getter-and-setter-methods
package version:
"jest": "^26.6.3",
"ts-jest": "^26.4.4"

Error: Need to call TestBed.initTestEnvironment() first

I'm trying do a test in angular of a service.
This is my part of the code
describe('AddressService', () => {
let service: AddressService;
let injector: TestBed;
let httpTestingController: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [AddressService]
});
injector = getTestBed();
service = injector.inject(AddressService);
httpTestingController = injector.inject(HttpTestingController);
// service = TestBed.inject(AddressService);
});
afterEach(() => {
httpTestingController.verify();
})
httpTestingController = TestBed.inject(HttpTestingController);
it('should be created', () => {
expect(service).toBeTruthy();
});
const dummyAddressListResponse = {
data: [
{direccion: 'address1'}, {Colas: 'queue1'},
{direccion: 'address2'}, {Colas: 'queue2'}
],
};
it('getAddress() should return data', () => {
service.getAddress().subscribe((res) => {
expect(res).toEqual(dummyAddressListResponse);
});
const req = httpTestingController.expectOne(`${environment.URI}/mock-address`);
expect(req.request.method).toBe('GET');
req.flush(dummyAddressListResponse);
})
});
At the moment of run the test ng test --main src/app/services/address/address.service.spec.ts
I'm seeing this error Error: Need to call TestBed.initTestEnvironment() first
I have searched and don't see any solution, Has it happened to someone?
For jest users - just add the following code in setup-jest.js.
Because jest needs to be initialized.
import { TestBed } from "#angular/core/testing";
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from "#angular/platform-browser-dynamic/testing";
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
the first thing: --main shouldn't be used, it points to an entrypoint, not to a desired test, and should be src/test.ts.
To run a single test use the next command:
ng test --include "app/services/address/address.service.spec.ts"
The test should be a bit different:
describe('AddressService', () => {
let service: AddressService;
let injector: TestBed;
let httpTestingController: HttpTestingController;
beforeEach(async () => {
// let's compile TestBed first.
await TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [AddressService],
}).compileComponents();
// let's use TestBed.injector.
service = TestBed.inject(AddressService);
httpTestingController = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpTestingController.verify();
})
it('should be created', () => {
expect(service).toBeTruthy();
});
it('getAddress() should return data', () => {
const dummyAddressListResponse = {
data: [
{direccion: 'address1'}, {Colas: 'queue1'},
{direccion: 'address2'}, {Colas: 'queue2'}
],
};
let actual: any;
service.getAddress().subscribe((res) => actual = res);
const req = httpTestingController.expectOne(`${environment.URI}/mock-address`);
expect(req.request.method).toBe('GET');
req.flush(dummyAddressListResponse);
expect(actual).toEqual(dummyAddressListResponse);
});
});
Is this using ng-packagr (i.e. an angular library)? If so you might want to check that there are no node_modules under the ./project/ folder.
This was throwing me this exact same error. The moment I deleted the node_modules under the project folder it all started to work again.
Source: https://github.com/ngneat/spectator/issues/546
The problem is that it must run everything from test.ts.
So instead of run ng test --main src/app/services/address/address.service.spec.ts command, just change const context = require.context('./', true, /\.spec\.ts$/); to const context = require.context('./', true, /address\.service\.spec\.ts$/); and use ng test command.

Jest Node.js Not able to call beforeAll and afterAll properly using separate file

I have defined beforAll and afterAll in a separate file bootstrap.js but I am not able to do integration testing. I am using serverless stack. I took help from github but that example was written in mocha so I tried to transform it to jest.
bootstrap.js
beforeAll(async () => {
console.log('[Tests Bootstrap] Start');
await startSlsOffline((err) => {
if (err) {
console.log(err);
}
console.log('[Tests Bootstrap] Done');
});
}, 30000);
afterAll(async () => {
console.log('[Tests Teardown] Start');
await stopSlsOffline();
console.log('[Tests Teardown] Done');
});
handler.test.js
describe('get Endpoints', () => {
const server = request(`http://localhost:3005`);
test('should run get example', async () => {
const res = await server.get('/example');
console.log('res', res.body);
});
});
My jest configuration is
module.exports = {
verbose: true,
bail: true,
coverageDirectory: 'output/coverage/jest',
setupFilesAfterEnv: [ './bootstrap.js' ]
};
The output I get is
> jest --config test/jest.config.js
FAIL test/handler.test.js
get Endpoints
✕ should run get example (38ms)
● get Endpoints › should run get example
connect ECONNREFUSED 127.0.0.1:3005
console.log test/bootstrap.js:6
[Tests Bootstrap] Start
console.log test/bootstrap.js:30
Serverless: Offline started with PID : 5587 and PORT: 3005
console.log test/bootstrap.js:18
[Tests Teardown] Start
console.log test/bootstrap.js:47
Serverless Offline stopped
console.log test/bootstrap.js:22
[Tests Teardown] Done
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 2.825s
Ran all test suites.
npm ERR! Test failed. See above for more details.
The global setup doesn't work the way you are expecting it to work. If you see the logs, your beforeAll logs are coming after your test executes. You should use different way to setup and teadown. Jest has concept of globalSetup and globalTeardown and I guess that fits better in your case. As part of this you can start and stop your server. The config will look like this
Read more here - https://jestjs.io/docs/en/configuration#globalsetup-string
module.exports = {
verbose: true,
bail: true,
coverageDirectory: 'output/coverage/jest',
globalSetup: "./bootstrap.js",
globalTeardown: "./bootstrap.js"
};
And your bootstrap will looks like this
const { spawn} = require('child_process');
let slsOfflineProcess;
module.exports = async () => {
console.log('[Tests Bootstrap] Start');
await startSlsOffline((err) => {
if (err) {
console.log(err);
}
console.log('[Tests Bootstrap] Done');
});
}
const startSlsOffline = (done) => {
if (slsOfflineProcess) {
slsOfflineProcess.kill('SIGINT');
console.log('Serverless Offline stopped');
done();
}
slsOfflineProcess = spawn('sls', [ 'offline', 'start', '--port', 3005 ]);
console.log(`Serverless: Offline started with PID : ${slsOfflineProcess.pid} and PORT: 3005`);
slsOfflineProcess.stdout.on('data', (data) => {
if (data.includes('Offline listening on')) {
console.log(data.toString().trim());
done();
}
});
slsOfflineProcess.stderr.on('data', (errData) => {
console.log(`Error starting Serverless Offline:\n${errData}`);
done(errData);
});
};
I solved my issue using this config with help of 1st answer using globalSetup and globalTeardown.
Read more here - https://jestjs.io/docs/en/configuration#globalsetup-string
bootstrap.js
const { spawn } = require('child_process');
let slsOfflineProcess;
module.exports = async () => {
console.log('[Tests Bootstrap] Start');
await startSlsOffline().catch((e) => {
console.error(e);
return;
});
global.__SERVERD__ = slsOfflineProcess;
};
function startSlsOffline() {
slsOfflineProcess = slsOfflineProcess = spawn('sls', [ 'offline', 'start', '--port', 3005 ]);
return finishLoading();
}
const finishLoading = () =>
new Promise((resolve, reject) => {
slsOfflineProcess.stdout.on('data', (data) => {
if (data.includes('Serverless: Offline [HTTP] listening on')) {
console.log(data.toString().trim());
console.log(`Serverless: Offline started with PID : ${slsOfflineProcess.pid}`);
resolve('ok');
}
if (data.includes('address already in use')) {
reject(data.toString().trim());
}
});
slsOfflineProcess.stderr.on('data', (errData) => {
console.log(`Error starting Serverless Offline:\n${errData}`);
reject(errData);
});
});
teardown.js
module.exports = async function() {
let slsOfflineProcess = global.__SERVERD__;
slsOfflineProcess.stdin.write('q\n');
slsOfflineProcess.stdin.pause();
await slsOfflineProcess.kill('SIGINT');
console.log('Serverless Offline stopped');
};
Find a sample on this link here: https://github.com/bilalsha/sls-test-jest
P.S globalTeardown do not works if test fails. I will post solution once I have it.

How to define redis client globally?

I have a simple component that is bind to a controller. So every HTTP request calls testFunction():
//test.js
const redisHelper = require('../myRedis');
class TEST {
testFunction() {
....
redisHelper.getCache(cacheKey)
.then((cacheData) => {
....
}
}
module.exports = TEST;
This component requires myRedis module:
//myRedis.js
const redis = require('redis');
class myRedis {
constructor () {
....
this.redisClient = this.getRedisClient();
}
getRedisClient () {
let redisClient;
....
return redisClient;
}
getCache (key) {
....
}
quit () {
this.redisClient.quit();
}
}
module.exports = new myRedis();
I don't want to create redis client for every HTTP request but keep it ON/open. So can I declare redis client in app.js and use it in test.js? Then on process exit I'll close the connection.
process.on('SIGTERM', () => {
server.close(() => {
redisClient.quit();
process.exit();
})
})
The problem is with redis client that crashes when it's open/close for every of thousands HTTP requests

testing node express endpoint and stub 3rd party api call

I have an express app like this:
server.js
const postsController = require('./controllers/posts_controller.js')
module.exports = app = express()
app.get('posts', postsController.index)
posts_controller.js
const post = require('./post')()
module.exports = {
index: (req, res) => {
post.getAll().then((posts) => {
res.status(200).send(posts)
}, (error) => {
res.status(400).send('text')
})
}
}
post.js
module.exports = () => {
const thirdPartyApi = require('third_party_api')
return {
get: () => {
// do some stuff
return thirdPartyApi.get().then((resp) => {
// do some more stuff
return Promise.resolve(resp)
})
}
}
}
spec/posts_controller_spec.js
const app = require('../server')
const request = require('supertest')
describe('GET /posts', () => {
it('should return a collection of posts', () => {
request(app)
.get('/posts')
.end((_err, resp) => {
expect(resp.status).toEqual(200)
})
})
})
My goal here is to stub out the thirdPartyApi.get(). I tried with proxyquire by adding this line to posts_controller_spec:
proxyquire('../posts_controller', {third_party_api: {
get: () => { console.log('stubbed out get method'); }
})
This doesn't work because the server.js file is the file that requires the third_party_api again.
I could do something like this to test the controller:
const postsController = proxyquire('../posts_controller', {third_party_api: {
get: () => { console.log('stubbed out get method'); }
})
postsController.index(req, res)
This second strategy doesn't feel right because now I have to stub req and res and now I'm bypassing the actual app instance.
Is there an easy way to do this, with proxyquire, or otherwise?
I realized what's going on, proxyquire is not actually messing up here.
the file post.js exports a function, so when posts_controller.js requires() the post.js file, it executes the function and the require for third_party_api is evaluated again and the stub is wiped out.
This is the "runtimeGlobal" scenario described here: https://www.npmjs.com/package/proxyquire#globally-override-require-during-module-runtime
Solution is to fix up post.js so that it doesn't export a function:
const thirdPartyApi = require('third_party_api')
return {
get: () => {
// do some stuff
return thirdPartyApi.get().then((resp) => {
// do some more stuff
return Promise.resolve(resp)
})
}
}
Now, as long as this happens before the app is initialized, the
proxyquire('../posts_controller', {third_party_api: {
get: () => { console.log('stubbed out get method'); }
})
Then third_party_api module that gets required inside the post controller is evaluated at load time and it gets cached as expected.

Categories

Resources