How to mock NODE_ENV in unit test using Jest - javascript

I want to set NODE_ENV in one of the unit test but it's always set to test so my tests fails.
loggingService.ts
...
const getTransport = () => {
if (process.env.NODE_ENV !== "production") {
let console = new transports.Console({
format: format.combine(format.timestamp(), format.simple()),
});
return console;
}
const file = new transports.File({
filename: "logFile.log",
format: format.combine(format.timestamp(), format.json()),
});
return file;
};
logger.add(getTransport());
const log = (level: string, message: string) => {
logger.log(level, message);
};
export default log;
loggingService.spec.ts
...
describe("production", () => {
beforeEach(() => {
process.env = {
...originalEnv,
NODE_ENV: "production",
};
console.log("test", process.env.NODE_ENV);
log(loglevel.INFO, "This is a test");
});
afterEach(() => {
process.env = originalEnv;
});
it("should call log method", () => {
expect(winston.createLogger().log).toHaveBeenCalled();
});
it("should not log to the console in production", () => {
expect(winston.transports.Console).not.toBeCalled();
});
it("should add file transport in production", () => {
expect(winston.transports.File).toBeCalledTimes(1);
});
});
...
How can I set process.env.NODE_ENV to production in my tests preferably in the beforeEach such that the if block in my service is false and the file transport is returned. I have omitted some code for the sake of brevity.

The core problem you are facing is caused by the fact that once you attempt to import the file that you are trying to test into your test suite - the code within it will be immediately evaluated and the implicitly invoked functions will be executed, meaning that logger.add(getTransport()); will be called before any of the functions like beforeEach have a chance to set the environment variables.
The only way to get around this is to use the following approach:
You will first need to assign the process.env.NODE_ENV environment variable to a const variable within another file. Let's call it environmentVariables.ts, and the following will be its contents:
export const ENVIRONMENT = process.env.NODE_ENV;
We will then have to refactor getTransport to use this variable as follows:
const getTransport = () => {
if (ENVIRONMENT !== "production") {
In your test suite, you will then have to mock out the const file which will allow you to change what the ENVIRONMENT variable is set to. Note ../src/environmentVariables is an example directory and you will have to actually define what the relevant directory of this file is. Additionally make sure that this is outside of the describe clause, preferably above for readability:
jest.mock('../src/environmentVariables', () => ({
ENVIRONMENT: 'production',
}));
Your unit tests will then execute with the ENVIRONMENT being production.

Related

Jest: Share Variables Between Test Files

I'll start with an example of how I set up my tests for a backend server. TL;DR at bottom.
This file represents my server:
//server.js
const express = require('express');
class BackendServer {
constructor(backingFileDestination, database) {
this.server = express();
/* Set up server ... */
/* Set up a mysql connection ... */
}
closeMySQLConnectionPool = () => {
/* Close the mysql connection ... */
};
}
module.exports = BackendServer;
In my package.json, I have the following:
"jest": {
"testEnvironment": "node",
"setupFilesAfterEnv": [
"<rootDir>/tests/setupTests.js"
]
}
Which allows me to use this setup file for my tests:
//setupTests.js
const { endpointNames } = require('../../src/common/endpointNames.js');
const BackendServer = require('../server.js');
const server = new BackendServer(
'../backing_files_test/',
'test',
);
const supertester = require('supertest')(server.server);
// drop, recreate, and populate the database once before any tests run
beforeAll(async () => {
await supertester.post(endpointNames.DROP_ALL_TABLES);
await supertester.post(endpointNames.CREATE_ALL_TABLES);
await supertester.post(endpointNames.POPULATE_ALL_TABLES);
});
// clean up the local setupTests server instance after all the tests are done
afterAll(async () => {
await server.closeMySQLConnectionPool();
});
Notice how I had to import the BackendServer class and instantiate it, then use that instance of it.
Now, I have other test files, for example test1.test.js:
//test1.test.js
const { endpointNames } = require('../../src/common/endpointNames.js');
const BackendServer = require('../server.js');
const server = new BackendServer(
'../backing_files_test/',
'test',
);
const supertester = require('supertest')(server.server);
// clean up the local server instance after all tests are done
afterAll(async () => {
await server.closeMySQLConnectionPool();
});
test('blah blah', () => {
/* Some test ... */
});
The problem is when I go to write test2.test.js, it will be the same as test1.test.js. This is the issue. For every test file, I need to instantiate a new server and then have a separate afterAll() call that cleans up that server's SQL connection. I can't stick that afterAll() inside of setupTests.js because it needs to operate on test1.test.js's local server instance.
TL;DR: Each of my test files instantiates a new instance of my server. What I want is to instantiate the server once in setupTests.js and then simply use that instance in all my tests. Is there a good way to share this single instance between all my test files?
I was able to figure out a way to achieve what I wanted. It involves instantiating variables in setupTests.js and then exporting getters for them.
//setupTests.js
const { endpointNames } = require('../../src/common/endpointNames.js');
const BackendServer = require('../server.js');
const server = new BackendServer(
'../backing_files_test/',
'test',
);
const supertester = require('supertest')(server.server);
// drop, recreate, and populate the database once before any tests run
beforeAll(async () => {
await supertester.post(endpointNames.DROP_ALL_TABLES);
await supertester.post(endpointNames.CREATE_ALL_TABLES);
await supertester.post(endpointNames.POPULATE_ALL_TABLES);
});
// clean up the local setupTests server instance after all the tests are done
afterAll(async () => {
await server.closeMySQLConnectionPool();
});
const getServer = () => { // <==== ADD THESE 2 FUNCTIONS
return server;
};
const getSupertester = () => { // <==== ADD THESE 2 FUNCTIONS
return supertester;
};
module.exports = { getServer, getSupertester }; // <==== EXPORT THEM
I added a couple functions to the end of setupTests.js that, when called, will return whatever the local variables point to at the time. In this case, server and supertester are declared with const, so I could have exported them directly I think, but in other cases I had some variables that I wanted to share that were declared with let, so I left it this way.
Now I have functions exported from setupTests.js that I can import in my test files like this:
//test1.test.js
const { endpointNames } = require('../../src/common/endpointNames.js');
const { getServer, getSupertester } = require('./setupTests.js');
test('blah blah', () => {
/* Some test using getSupertester().post() or getServer().someFunction() ... */
});
So now I can have local variables inside setupTests.js that are accessible in my .test.js files.
This makes my whole testing process cleaner because I only need to set up and tear down one server, which means only 1 connection pool for my SQL server and less code duplication of having to instantiate and clean up a new server in every .test.js file.
Cheers.

It is possible to mock .env file using jest.mock?

It is possible to mock .env file using jest.mock? or maybe js works only with regular js models (js files)
my tests/test.spec.ts:
jest.mock('../.env')
description(...
You're able to use jest.mock() with any file that you use in the implementation with require or import.
But since you probably don't import/require the .env file, you might want to modify some environment variable, where beforeAll is really handy.
For example:
import func from 'module';
describe('environment dependant function', () =>{
describe('development', () => {
beforeAll(() => {
process.env.NODE_ENV = 'development'
});
it('should report development', () => {
expect(func()).toEqual('You are on DEV');
});
});
describe('production', () => {
beforeAll(() => {
process.env.NODE_ENV = 'production'
});
it('should report development', () => {
expect(func()).toEqual('You are on PROD');
});
});
});

Testcafe - Test command line argument outside test case

As I'm getting familiar with Testcafe, I'm trying to use a command line argument to give the user more information on how to run tests. For that reason, I'm using the minimist package.
However, I cannot print or use any variables outside the test cases. Please find below my code.
import { Selector } from 'testcafe';
import minimist from 'minimist';
const args = minimist(process.argv.slice(2));
const env = args.env;
console.log('*** A SAMPLE CONSOLE OUTPUT ***'); // does not print
fixture `Getting Started`
.page `http://devexpress.github.io/testcafe/example`;
test('My first test', async t => {
console.log('*** ANOTHER SAMPLE CONSOLE OUTPUT ***'); // prints
await t
.typeText('#developer-name', 'John Smith')
.wait(1000)
.click('#submit-button')
// Use the assertion to check if the actual header text is equal to the expected one
.expect(Selector('#article-header').innerText).eql('Thank you, John Smith!');
});
I want to write an if statement that checks if env === '' or use a default argument.
How can I accomplish this?
However, I cannot print or use any variables outside the test cases.
 
Please use a programming way to run TestCafe.
I've changed you code example (test.js) and created a file that runs TestCafe programmatically (run.js).
Put these files into a folder and perform command 'node run.js --env value' in your terminal.
Then you will see the following output:
'*** A SAMPLE CONSOLE OUTPUT ***'
Getting Started
value
test.js
import { Selector } from 'testcafe';
import minimist from 'minimist';
const args = minimist(process.argv.slice(2));
const env = args.env;
console.log('*** A SAMPLE CONSOLE OUTPUT ***');
fixture `Getting Started`
.page `http://devexpress.github.io/testcafe/example`;
test('My first test', async t => {
console.log(env); // prints
await t
.typeText('#developer-name', 'John Smith')
.wait(1000)
.click('#submit-button')
.expect(Selector('#article-header').innerText).eql('Thank you, John Smith!');
});
run.js
const createTestCafe = require('testcafe');
let runner = null;
createTestCafe('localhost', 1337, 1338, void 0, true)
.then(testcafe => {
runner = testcafe.createRunner();
})
.then(() => {
return runner
.src('test.js')
.browsers('chrome')
.run()
.then(failedCount => {
console.log(`Finished. Count failed tests:${failedCount}`);
process.exit(failedCount)
});
})
.catch(error => {
console.log(error);
process.exit(1);
});
A solution to accomplish this is:
1) Create a separate config.js file that will handle your custom command-line options:
import * as minimist from 'minimist';
const args = minimist(process.argv.slice(2));
// get the options --env=xxx --user=yyy from the command line
export const config = {
env: args.env,
user: args.user,
};
2) In you test file:
remove any code outside the fixture and the test methods.
import the config file and inject it in the TestController context
get the command args via the TestController context
import 'testcafe';
import { Selector } from 'testcafe';
import { config } from './config';
fixture('Getting Started')
.beforeEach(async (t) => {
// inject config in the test context
t.ctx.currentConfig = config;
});
test('My first test', async (t) => {
// retrieve cli args from the test context
const currentConfig = t.ctx.currentConfig;
console.log(`env=${currentConfig.env}`);
});

Configuring jsdom in Jest across multiple tests without using modules

I want to test scripts in an environment where we can not export modules. I have installed Jest version 23.1.0 and there aren't other packages in my package.json file.
Using jsdom 'old' api I have come up with a solution that works as expected:
script.js
var exVar = "test";
script.test.js
const jsdom = require('jsdom/lib/old-api.js');
test('old jsdom api config', function(done) {
jsdom.env({
html: "<html><body></body></html>",
scripts: [__dirname + "/script.js"],
done: function (err, window) {
expect(window.exVar).toBe("test");
done();
}
});
});
However with this implementation I have to re-write the config for every test, because it looks like the jsdom config gets re-written every time.
What I have tried
So far I have tried running this configuration:
const jsdom = require('jsdom/lib/old-api.js');
jsdom.env({
html: "<html><body></body></html>",
scripts: [__dirname + "/script.js"],
done: function (err, window) {
console.log('end');
}
});
with this test:
test('old jsdom api config', function(done) {
expect(window.exVar).toBe("test");
done();
});
in different ways: inside beforeAll, inside a script linked through setupFiles or through setupTestFrameworkScriptFile in the Jest configuration object, but still nothing works.
Maybe I could extend jest-environment as suggested in the docs, but I have no idea of the syntax I should be using, nor of how to link this file to the tests.
Thanks to my co-worker Andrea Talon I have found a way of using the same setup for different tests (at least inside the same file) using the 'Standard API' (not the 'old API').
Here is the complete test file.
const {JSDOM} = require("jsdom")
const fs = require("fs")
// file to test
const srcFile = fs.readFileSync("script.js", { encoding: "utf-8" })
// the window
let window
describe('script.js test', () => {
beforeAll((done) => {
window = new JSDOM(``, {
runScripts: "dangerously"
}).window
const scriptEl = window.document.createElement("script")
scriptEl.textContent = srcFile
window.document.body.appendChild(scriptEl)
done()
})
test('variable is correctly working', (done) => {
expect(window.exVar).toBe("test");
done()
})
})
Additional setup
In order to load multiple scripts I have created this function which accepts an array of scripts:
function loadExternalScripts (window, srcArray) {
srcArray.forEach(src => {
const scriptEl = window.document.createElement("script")
scriptEl.textContent = src
window.document.body.appendChild(scriptEl)
});
}
So instead of appending every single script to the window variable I can load them by declaring them at the top of the file like this:
// files to test
const jQueryFile = fs.readFileSync("jquery.js", { encoding: "utf-8" })
const srcFile = fs.readFileSync("lib.js", { encoding: "utf-8" })
and then inside the beforeAll function I can load them altogether like this:
loadExternalScripts(window, [jQueryFile, srcFile])

Jest - Testing Module Multiple Times in One Test Suite

I have a TypeScript module (should be irrelevant, as I think this affect JS too) and I'm trying to test a module I have. The module imports lots of data from external files and chooses which data should be returned based on the a variable.
I'm attempting to run some tests where I update that variable, re-require the module and run further tests in one file. But my issue is that the require of the file only runs once. I guess it's being cached. Is it possible to tell Jest's require function not to cache or to clear the cache between tests?
Here's some stripped back code of what I'm trying to achieve:
module.ts
import { getLanguage } from "utils/functions";
import * as messagesEn from "resources/translations/en";
import * as messagesFr from "resources/translations/fr";
// Determine the user's default language.
const language: string = getLanguage();
// Set messages based on the language.
let messages: LocaleMessages = messagesEn.default;
if (languageWithoutRegionCode === "fr") {
messages = messagesFr.default;
}
export { messages, language };
test.ts
import "jest";
// Mock the modules
const messagesEn = { "translation1": "English", "translation2": "Words" };
const messagesFr = { "translation1": "Francais", "translation2": "Mots" };
const getLangTest = jest.fn(() => "te-ST");
const getLangEn = jest.fn(() => "en-GB");
const getLangFr = jest.fn(() => "fr-FR");
jest.mock("resources/translations/en", () => ({"default": messagesEn}));
jest.mock("resources/translations/fr", () => ({"default": messagesFr}));
jest.mock("utils/functions", () => ({
getLanguage: getLangTest
})
);
describe("Localisation initialisation", () => {
it("Sets language", () => {
const localisation = require("./localisation");
expect(getLangTest).toHaveBeenCalled();
expect(localisation.language).toEqual("te-ST");
expect(localisation.messages).toEqual(messagesEn);
});
it("Sets english messages", () => {
// THIS GETS THE MODULE FROM THE CACHE
const localisation = require("./localisation");
expect(getLangEn).toHaveBeenCalled();
expect(localisation.language).toEqual("en-GB");
expect(localisation.messages).toEqual(messagesEn);
});
it("Sets french messages", () => {
// THIS GETS THE MODULE FROM THE CACHE
const localisation = require("./localisation");
expect(getLangFr).toHaveBeenCalled();
expect(localisation.language).toEqual("fr-FR");
expect(localisation.messages).toEqual(messagesFr);
});
});
I'm aware the second and third tests won't work anyway as I'd need to update the "utils/functions" mock. The issue is that the code in module.ts only runs once.
So, many thanks to the Jest folks on Discord. It's possible to actually clear the modules from the cache with the jest.resetModules() function.
So my test.ts file will look as follows:
describe("Localisation initialisation", () => {
beforeEach(() => {
jest.resetModules();
});
it("Sets language", () => {
const localisation = require("./localisation");
// Perform the tests
});
it("Sets english messages", () => {
const localisation = require("./localisation");
// Perform the tests
});
it("Sets french messages", () => {
const localisation = require("./localisation");
// Perform the tests
});
});
The beforeEach() call to jest.resetModules() ensures we're re-running the code in the module.

Categories