I was wondering how I would incorporate the esm package https://www.npmjs.com/package/esm with jest on a node backend.
I tried setting up a setup file with require("esm") and require("esm")(module) at the very top of the file, but it's still giving me the SyntaxError: Unexpected token error.
I would have previously used node -r esm but jest doesn't support this.
When you perform require("esm")(module), think of it as you are creating an esm-transformer function that is pending a file to be transformed into an ES module.
Here's my attempt with node v8+ with:
default jest configuration
default esm configuration
utils-1.js:
export const add = (a, b) => a + b;
utils-2.js:
export const multiAdd = array => array.reduce((sum, next) => sum + next, 0)
_test_/utils-1.assert.js
import { add } from '../utils-1';
describe('add(a,b)', () => {
it('should return the addtion of its two inputs', () => {
expect(add(1,2)).toBe(3);
});
});
_test_/utils-2.assert.js
import { multiAdd } from '../utils-2';
describe('multiAdd(<Number[]>)', () => {
it('should return a summation of all array elements', () => {
expect(multiAdd([1,2,3,4])).toBe(10);
})
});
_test_/utils.test.js
const esmImport = require('esm')(module);
const utils_1 = esmImport('./utils-1.assert')
const utils_2 = esmImport('./utils-2.assert')
Hope this helps!
Related
I am trying to write unit testing using Jest for a Node JS project. It was importing all the modules using require.main.require
Below are the simulation of the issue. Code can be found here: https://stackblitz.com/edit/node-jest-demo?file=index.js
I have the following test file present in my root directory in which I am importing index.js
./sample.pass.test.js
const { checkUser } = require('./index');
console.log(checkUser); // This is purely to check If i can access checkUser from this file or not
describe('Testing...', () => {
it('Should pass', () => {
expect(0).toBe(0);
});
});
In my index.js I am importing another function using require.main.require
const { getUserById } = require.main.require('./models/UserModel');
function checkUser(id) {
const user = getUserById(id);
return user ? 'Found' : 'Not Found';
}
module.exports.checkUser = checkUser;
The above test case is passing. But If I were to place the same test file in some other directly (like _test_ ) then it fails.
E.g.: ./__test__/sample.fail.test.js
Notice here I adjusted require statement of index since it is now one level up
const { checkUser } = require('../index');
console.log(checkUser);
describe('Testing...', () => {
it('Should pass', () => {
expect(0).toBe(0);
});
});
The result shows it is unable to access UserModel.
Cannot find module './models/UserModel' from '__tests__/sample.fail.test.js'
Require stack:
index.js
__tests__/sample.fail.test.js
> 1 | const { getUserById } = require.main.require('./models/UserModel');
| ^
2 |
3 | function checkUser(id) {
4 | const user = getUserById(id);
What could be the solution in this case?
Thank in advance!
I'm trying to write out a unit test that stubs the getSignedUrl function from the #aws-sdk/s3-request-presigner package, however when I try stub out the function with sinon, I receive the error:
TypeError: Descriptor for property getSignedUrl is non-configurable and non-writable
const s3RequestSigner = require("#aws-sdk/s3-request-presigner");
const expect = require('chai').expect;
const sinon = require('sinon')
....
it('should throw an error when getSignedUrl rejects', async function() {
const sandbox = sinon.createSandbox();
sandbox.stub(s3RequestSigner, "getSignedUrl").rejects("fakeUrl");
sandbox.restore();
})
I'm using node.js 16 and writing javascript rather than typescript. Is there a way to mock out my function, i'm struggling to write my tests otherwise?
I came up with the following workaround for ES6 modules. You can wrap getSignedUrl in your own module and mock that module instead. This approach should work for any modules where sinon is unable to mock a "non-configurable and non-writable" method.
For example:
my-s3-client-internals.js - Your custom wrapper module
// You'll need to import the original method, assign it to
// a new const, then export that const
import { getSignedUrl as getSignedUrl_orig } from '#aws-sdk/s3-request-presigner';
export const getSignedUrl = getSignedUrl_orig;
my-s3-client.js - Consumer of getSignedUrl
// Import the method instead from your custom file
import { getSignedUrl } from './my-s3-client-internals';
// Call it however you normally would, for example:
export const getUrl(bucket, key) {
const command = new GetObjectCommand({ Bucket: bucket, Key: key });
return getSignedUrl(client, command, { expiresIn: 300 });
}
my-s3-client.spec.js - Unit tests for the consumer module
import { getUrl } from './my-s3-client';
import * as clientInternals from './my-s3-client-internals';
import sinon from 'sinon';
it('does something', () => {
// Mock the method exported from your wrapper module
sinon.stub(clientInternals, 'getSignedUrl')
.callsFake(async (client, command, options) => {
return 'fake-url';
});
// Then call your consumer method to test
const url = await getUrl('test-bucket', 'test-key');
expect(url).to.equal('fake-url');
});
So I won't make this the official answer, unless there are no better solutions, but this is what my research has brought about a solution.
The issue is related to this: https://github.com/sinonjs/sinon/issues/2377
Where sinon will throw an error when the Object.descriptor is non-configurable.
There is no obvious way around that currently, that I can find. The way to solve it is to use proxyquire:
const sinon = require('sinon')
const proxyquire = require('proxyquire')
...
it('should throw an error when getSignedUrl rejects', async function() {
const fakeurl = 'hello world'
const fakeURL = sinon.stub().resolves(fakeurl)
const handler = proxyquire(
'../../handlers/presigned_url',
{
'#aws-sdk/s3-request-presigner': {
'getSignedUrl': async () => {
return fakeURL()
}
}
}
)
This will then resolve with whatever you want fakeurl to be.
Another possible solution is to use mockery. E.g. to mock uuid
import { expect } from 'chai';
import mockery from 'mockery';
import sinon from 'sinon';
describe('domain/books', () => {
let createBook;
let uuidStub;
before(async () => {
mockery.enable({
warnOnReplace: false,
warnOnUnregistered: false,
});
uuidStub = sinon.stub();
mockery.registerMock('uuid', { v4: uuidStub });
({ createBook } = await import('../domain/books.js'));
});
afterEach(() => {
sinon.resetHistory();
});
after(() => {
sinon.restore();
mockery.disable();
mockery.deregisterAll();
});
describe('createBook', () => {
it('should save a book and return the id', () => {
const id = 'abc123';
uuidStub.returns(id);
const { id: bookId } = createBook({
title: 'My Book',
author: 'Jane Doe',
});
expect(bookId).to.equal(id);
});
});
});
The mockery setup is a bit tedious, but the library saved me a number of times.
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));
How to mock not installed npm package in jest?
I'm writing a library and I need to test some cases when optional dependencies are not installed.
Update
My library has an optional dependency. The end-user of my library can optionally to install styled-components.
In my tests (jest) I covered the case when styled-components is installed.
Now I need to cover the case when the package is not installed.
test(`When styled-components is not installed`, () => {
process.env.SC_NOT_INSTALLED = true
const fn = () => {
const styled = require(`./styled`)
}
expect(fn).toThrow(Error)
})
let styled
try {
require.resolve(`styled-components`)
styled = require(`styled-components`)
if (process.env.NODE_ENV === `test` && process.env.SC_NOT_INSTALLED) {
throw new Error(`Imitation styled-components is not installed`)
}
}
catch {
styled = () => {
throw new Error(`Module not found: styled-components`)
}
}
export default styled
process.env.SC_NOT_INSTALLED -> will not work because as I guess the test are running in different process.
When an exception is thrown in your try you are exporting a function.
Calling the exported function is what throws the Error.
Change your test to this:
test(`When styled-components is not installed`, () => {
process.env.SC_NOT_INSTALLED = true;
const styled = require(`./styled`).default;
expect(() => styled()).toThrow('Module not found: styled-components'); // Success!
});
...and it should work.
Update
If you are calling require('./styled') multiple times in the same test file, then you will want to add an afterEach that calls jest.resetModules, otherwise Jest will cache the module and just keep returning the same module for each require:
afterEach(() => {
jest.resetModules();
})
test(`When styled-components is installed`, () => {
const styled = require(`./styled`).default;
// ...
});
test(`When styled-components is not installed`, () => {
process.env.SC_NOT_INSTALLED = true;
const styled = require(`./styled`).default;
expect(() => styled()).toThrow('Module not found: styled-components'); // Success!
});
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.