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

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');
});
});
});

Related

How to mock NODE_ENV in unit test using Jest

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.

Use jest to compare content of two files

I'm trying to switch from Mocha and Chai to Jest. In my current setup I'm also using chai-files to compare the contents of two files:
import chai, { expect } from 'chai';
import chaiFiles, { file } from 'chai-files';
import fs from 'fs-extra';
import { exec } from 'child-process-promise';
chai.use(chaiFiles);
describe('cli', () => {
before(() => {
process.chdir(__dirname);
});
it('should run', async () => {
// make a copy of entry file
fs.copySync('./configs/entry/config.version-and-build.xml', './config.xml');
// executes code that changes temp files
await exec('../dist/cli.js -v 2.4.9 -b 86');
// checks if target file and change temp file are equal
expect(file('./config.xml')).to.equal(file('./configs/expected/config.version-and-build.to.version-and-build.xml'));
});
afterEach(() => {
if (fs.existsSync(tempConfigFile)) {
fs.removeSync(tempConfigFile);
}
});
});
How should this be done in Jest? Will I need to load both files and compare the content?
Yes, simply load the contents of each like so:
expect(fs.readFileSync(actualPath)).toEqual(fs.readFileSync(expectedPath));

Jest global teardown runs before tests finish?

I'm trying to make sure my app gets properly destroyed after all my Jest tests have run, but I'm running into some very strange behaviour trying to use Jest's global teardown config value.
Here's the situation: my app creates a database connection. It also has a destroy method that closes the database connection. This works.
I have a single test that starts the server, runs a query against the database connection. In my global teardown function, I call app.destroy(), but the process hangs.
If I comment out the destroy call in the global teardown function and put app.destroy() in my test after the query, Jest doesn't hang and closes like it's supposed to. I can also put afterAll(() => app.destroy()) in my test file and things work properly.
Here is my jest.config.js
module.exports = {
testEnvironment: 'node',
roots: [
'<rootDir>/src'
],
transform: {
'^.+\\.tsx?$': 'ts-jest'
},
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
moduleFileExtensions: [
'ts',
'tsx',
'js',
'jsx',
'json',
'node'
],
globalSetup: '<rootDir>/src/testSetup.ts',
globalTeardown: '<rootDir>/src/testTeardown.ts'
};
Here is the test (it's currently the only test in the app):
import app from '../..';
describe('User router', () => {
it('Should respond with an array of user objects', async () => {
await app.models.User.query();
});
});
And here is the global teardown in <rootDir>/src/testTeardown.ts:
import app from './index';
module.exports = async function testTeardown() {
await app.destroy();
};
Using the code above, the process hangs after tests finish. I've tried adding a console.log to testTeardown and the end of the test, and the logs happen in the correct order: test logs, then teardown logs. However if I move app.destroy() into my test it works perfectly:
import app from '../..';
describe('User router', () => {
it('Should respond with an array of user objects', async () => {
await app.models.User.query();
await app.destroy();
});
});
This also works:
import app from '../..';
afterAll(() => app.destroy());
describe('User router', () => {
it('Should respond with an array of user objects', async () => {
await app.models.User.query();
});
});
Why is this happening?
Also just for shits and giggles I tried setting a global._app in the test and then checking it in the teardown handler, but it was undefined. Do Jest's setup/teardown functions even get run in the same process as the tests?
No, jest globalSetup and globalTeardown files don't necessarily get run in the same process as your tests. This is because jest parallelises your tests and runs each test file in a separate process, but there is only one global setup/teardown phase for the combined set of test files.
You can use setupFiles to add a file that gets run in process with each test file. In the setupFiles file you can put:
afterAll(() => app.destroy());
Your jest config is just
module.exports = {
testEnvironment: 'node',
roots: [
'<rootDir>/src'
],
transform: {
'^.+\\.tsx?$': 'ts-jest'
},
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
moduleFileExtensions: [
'ts',
'tsx',
'js',
'jsx',
'json',
'node'
],
setupFiles: ['<rootDir>/src/testSetup.ts']
};
For the latest version of jest just include this configuration option in your jestconfiguration file
// globalSetup: '<rootDir>/tests_setup/testSetup.ts',
// globalTeardown: '<rootDir>/tests_setup/testTearDown.ts',
setupFiles: [
'<rootDir>/tests_setup/testSetup.ts',
'<rootDir>/tests_setup/testTearDown.ts',
],
With this Jest will automatically load the setup and tearDown functions automatically
All you need to do is to export a function that sets up and runs the beforeAll and another that runs the afterAll i.e
const tearDownTests = async () => {
beforeAll(async () => {
console.log('Test setup');
mockMongoose.prepareStorage().then(() => {
mongoose.connect('mongodb://localhost/test');
mongoose.connection.on('connected', () => {
console.log('db connection is now open');
});
});
});
};
export default tearDownTests;
and
const tearDownTests = async () => {
afterAll(async () => {
console.log('Finished on tests');
await mongoose.disconnect();
await mockMongoose.killMongo();
});
};
export default tearDownTests;

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