Restore between tests with Hapi Lab - node.js

Context:
I am using Hapi Lab to run a bunch of tests.
I also have an additional set of files that export mock JSON data.
What's the issue:
Each of the tests manipulate the JSON files as part of their routine.
The first test always passes, because it's dealing with the 'clean' JSON data, whereas following tests sometimes fail because the previous test(s) modified the JSON when the previous test(s) ran.
Similarly, if I run tests specifically one file at a time, the output is fine. Whereas if I attempt to test a directory that contains multiple tests, I get failures due to the malformed JSON.
npm run lab /test-directory/test1.js // Works fine
npm run lab /test-directory/test2.js // Works fine
vs
npm run lab /test-directory //Fails
What's the question:
Is there a convenient way of getting Lab to 'restore' the original state between tests instead of allowing the same instance of JSON data to leak between different tests?

I found that a convenient workaround for this issue was to run each test in its own worker using node child processes.
I added a file run-tests-sequentially.js which calls the tests in turn:
const { promisify } = require('util');
const { exec } = require('child_process');
const execAsync = promisify(exec);
(async () => {
console.log('Running test1...');
const { stdout: test1Output } = await execAsync('npm run test:integration /test-directory/test1.js');
console.log('Completed test1...');
console.log(test1Output);
console.log('Running test2...');
const { stdout: test2Output } = await execAsync('npm run test:integration /test-directory/test2.js');
console.log('Completed test2...');
console.log(test2Output);
})();
I then called node ./run-tests-sequentially.js to run the sequence.
Each test ran in a 'clean' worker, with no data leaks.

Related

Express +Jest. Test files are running sequentially instead of in parallel

I have an Express.JS server which uses jest and supertest as a testing framework.
It has been working excellently.
When I call my test npm script, it runs npx jest and all of my test files run in parallel.
However I ran my tests recently and they ran sequentially which takes a very long time, they have done this ever since.
I haven't changed any jest or npm configuration, nor have I changed my test files themselves.
Has anyone experienced this? Or is it possible that something in my configuration is incorrect?
jest.config
export default {
setupFilesAfterEnv: ['./__tests__/jest.setup.js'],
}
jest.setup.js
import { connectToDatabase } from '/conn'
// Override the dotenv to use different .env file
require('dotenv').config({
path: '.env.test',
})
beforeAll(() => {
connectToDatabase()
})
test('', () => {
// just a dummy test case
})
EDIT: Immediately after posting the question, I re ran the tests and they ran in parallel, without me changing anything. If anyone has any knowledge around this i'd be interested to get a second opinion
After intermittent switching between parallel and sequential for unknown reasons. I have found it work consistently by adding the --no-cache arg to the npx jest call.
See below where I found the answer
Github -> jest not always running in parallel

Jest test doesn't create the files the normal program would

I'm using node and puppeteer to load a page, get its content and then create a screenshot if it. At the end of the run function I have the following lines
var content = fs.writeFileSync(outputFilePath, processedContent);
var screenshot = page.screenshot({path: '../output/whatever.png', fullPage:true})
browser.close();
This works when running the node app. For testing I am using JEST
And when trying to run the JEST test that checks for the screenshot:
it('Run should take a screenshot', async() => {
const runResult = await run();
const screenshot = fs.readFileSync('/app/output/whatever.png');
expect(screenshot).toBeTruthy();
})
I get the following error ENOENT: no such file or directory, open '/app/output/whatever.png'
I'm having a hard time understanding why in the normal app flow the program creates the files when running but in the tests it doesn't. As additional info the entire thing runs in a Docker container
It is most likely because you are using an absolute path instead of a relative path in your jest test.
So instead of
const screenshot = fs.readFileSync('/app/output/whatever.png');
write
const screenshot = fs.readFileSync('./app/output/whatever.png');
to use a relative path
Also keep in mind your relative path should be from the the project root

Jest - do something before every test

Jest provides some useful methods for executing something before your tests: beforeEach() and beforeAll(). Jest docs on setup
The issue with these is they can only be placed inside of a describe block. So if I have many files, each with their own describe block, I need to place to beforeEach() in every file.
How can I run some code before & after every test, while only adding it once (instead of adding it in every describe block)?
You can try the globalSetup Jest config key. It is an optional key and it can be used to run an async function once before all test suites.
Please see https://github.com/facebook/jest/blob/master/docs/Configuration.md#globalsetup-string
Example of setting up globalSetup in package.json:
jest: {
globalSetup: "./path-to-global-setup.js"
}
... or in jest.config.js:
module.exports = {
globalSetup: "./path-to-global-setup.js"
};
Example of global-setup.js file:
module.exports = async function() {
// do something
};
This file will not be transformed by babel when you run your test suite.

Why can't get the global variable which was set in globalSetup in test code?

I use Jest to do unit test in node.
And I use the new feature globalSetup which come in Jest v22.
I have defined a global variable in globalSetup.
But I can't get it in the test code. Console log is undefined.
Anyone in this question?
Thanks.
Jest version: 22.0.0
node version: 8.9.0
yarn version: 1.3.2
OS: mac High Sierra 10.13.2
The code as follow:
// package.json
{
"jest": {
"globalSetup": "<rootDir>/src/globalSetupTest.js"
}
}
// globalSetupTest.js
module.exports = async function() {
global.foo = 'foo';
console.log(`global setup: ${global.foo}`);
};
// App.test.js
describe('APP test', () => {
it('renders without crashing', () => {
console.log({ foo: global.foo });
});
});
// test result
yarn run v1.3.2
$ node scripts/test.js --env=node --colors
global setup: foo
PASS src/App.test.js
APP test
✓ renders without crashing (5ms)
console.log src/App.test.js:3
{ foo: undefined }
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.354s, estimated 1s
Ran all test suites.
There's a solution offered from Jest people themselves: https://jestjs.io/docs/en/puppeteer.html. Note that if you're using CRA, this won't work out of the box (solution below), cause it currently doesn't support testEnvironment option for Jest.
Anyways:
In Jest config file you setup paths to global setup, teardown and test environment scripts
In global setup script, you create a browser and write it's WSEndpoint to a file
In global teardown script, you simply close the browser
In testEnvironment script, you read WSEndpoint from the file you saved before and then use it to connect to the running browser - after this, browser is available in your tests by using a global variable
If you're using CRA, you can use a custom setup for these tests and run them completely separately. And if you're using Puppeteer for e2e tests, this is probably what you want to do anyway.
You just add another script to your package.json: "test:e2e": "jest -c ./jest-e2e.config.js" and set it up as you want. Now you will have npm run test for unit tests and npm run test:e2e for end to end tests.
For what I understood is a design decision of Jest because is considered a bad practice to share state across different tests. Tests run in parallel and they should keep their own state.
See:
https://github.com/facebook/jest/issues/6007#issuecomment-381743011
https://github.com/facebook/jest/issues/4118
Can you try..
global.foo = 'foo';
console.log(`global setup: ${global.foo}`);
(remove the exports)
You can try changing globalSetup to setupFiles. That one, won't expect a function.
https://facebook.github.io/jest/docs/en/configuration.html#setupfiles-array

Automate Functional Testing in node.js

I checked some NPM libraries to test a webpages or web-services. But all of them expect that the server is already running. Since I want to automate functional testing, how can I setup NPM package in such a way that
It can start the server
Test the application
Stop the server
So that I can test it locally, as well as on online CI tools like travis-ci or circleci.
Case 1: Webservice
I wrote a NPM package which starts nodejs HTTP(s) server. It can be started from command line $stubmatic. Currently, I use 2 approaches to test it,
manual : I manually start it from command line. Then run the tests.
Automatic: I use exec module to run a unix command which can start the application and run pkill command to kill the application. But for this automation, my application need to be installed on testing machine.
Case 2: Website
I have create a NPM package: fast-xml-parser and created a demo page within the repo so that I can be tested in the browser. To test the demo page, I currently start a local server using http-server npm package manually. Test the application.
What can be the better way to write automate functional tests for node js applications?
Note:
I never used task runners like gulp or grunt. So I'm not sure if they can help in this case.
In case 1, my application starts node js native HTTP server. I'm not using any 3rd party application like express currently.
This question mentions a new Docker container system for Travis that could be duplicated locally. It might be a way: How to run travis-ci locally
Did you look at supertest (SuperAgent driven library for testing HTTP servers) and expect (Assertions library)(documented here) with mocha (Test Framework)?
I use them and I never had any kind of problems for all the tests I do until now.
The documention in the links contains all the informations you need to build up your test.
Case 1: Webservice
Problem 1
As nodejs server.close() was not working. I copied paste this snippet in every test file which is starting my webservice.
try{
server.setup(options);
server.start();
}catch(err){
console.log(err);
}
Once all the tests are completed, server stops.
**Problem 2
I was using chai-http incorrectly. Here is the complete working solution.
//Need to be placed before importing chai and chai-http
if (!global.Promise) {
global.Promise = require('q');
}
var server = require('.././lib/server');
var chai = require('chai')
, chaiHttp = require('chai-http');
chai.use(chaiHttp);
try{
server.setup(someoptions);
server.start();
}catch(err){
console.log(err);
}
describe('FT', function () {
describe('scenario::', function () {
it('responds to POST', function (done) {
chai.request("http://localhost:9999")
.post('/someurl')
.then(res => {
expect(res.status).toBe(200);
//console.log(res.text);
done();
}).catch( err => {
console.log(err);
done();
});
});
});
Case 2: Website This was quite simple.
I used http-server to start the server so my html files can be accessed.
I used zombie js for browser testing. (There are many other options available for browser testing)
Here is the code
process.env.NODE_ENV = 'test';
const Browser = require('zombie');
const httpServer = require('http-server');
describe("DemoApp", function() {
var browser = new Browser({site: 'http://localhost:8080'});
var server = httpServer.createServer();
server.listen(8080);
beforeEach(function(done){
browser.visit('/', done);
});
describe("Parse XML", function() {
it("should parse xml to json", function(done) {
browser.pressButton('#submit');
browser.assert.text('#result', 'some result text');
done();
});
});
afterEach(function(){
server.close();
})
});

Resources