How to mock a worker for a Jest test? - javascript

I have a test:
import { convertHeicToPng } from './heicUtils';
class Worker {
url: string;
onmessage: (m?: any) => void;
constructor(stringUrl: string) {
this.url = stringUrl;
this.onmessage = () => {};
}
postMessage(msg: any) {
this.onmessage(msg);
}
}
(window.Worker as any) = Worker;
describe('test heicUtils', () => {
test('should convert HEIC to PNG', async () => {
const file = new File([''], 'test.heic', { type: 'image/heic' });
const base64 = await convertHeicToPng(file);
expect(base64).toContain('data:image/png;base64');
});
});
and in heicUtils, I'm using heic2any, which uses WebWorkers. How can I properly mock a Worker for a Jest test?

Since you are testing your heicUtils module, you should mock the heic2any lib, otherwise, you will be testing the 3rd party library instead of your own code.
In the mock, you should define the functions/methods of heic2any that your heicUtils use and what they should return for each test case you intend to write.
Examples of how to mock modules can be found here: https://jestjs.io/docs/manual-mocks

tsc && mocha --reporter spec -t 5000 --exit
.npm install mocha
.then do this cmd
.Examples my github : https://github.com/www778878net/koa78-base78

Related

How to config testcafe basic configuration on ES modules

TypeScript and CoffeeScript
The testcafe documentation says that no additional settings are needed to use ES modules when writing tests, however, it's not clear how to configure the testcafe configuration if the project uses ES modules, for example to write global hooks. Because it looks line you have only 2 options to config testcafe globaly: .json and CommomJS
I need authorization before each test in the project, and I have this function for that:
import { Role, Selector, t } from 'testcafe';
export const user = Role('http://localhost:3000/login', async t => {
await t
.typeText(Selector('#loginInput'), 'Login')
.typeText(Selector('#passwordInput'), 'Password')
.click(Selector('button').withAttribute('data-testid', 'submitButton'));
});
And I have tried this in .testcafers.js file
import { user } from './src/testing/utilities/loginUser';
export default {
hooks: {
testRun: {
before: async () => {
await t.useRole(user)
}
},
}
};
To summarize, how can I write a global hook for testcafe, if my project is using ES modules
There is an exception for the config file. You should use CommonJS syntax as described in the documentation.
const { user } = require('./src/testing/utilities/loginUser');
module.exports = {
hooks: {
testRun: {
before: async () => {
await t.useRole(user)
}
},
}
};

Trying to stub a function results in Descriptor for property is non-configurable and non-writable

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.

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.

How to mock not installed npm package in jest?

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

How to mock a module that is required in another module with Jasmine

const Client = require('./src/http/client');
module.exports.handler = () => {
const client = new Client();
const locationId = client.getLocationId(123);
};
How can I test this module asserting that the client.getLocationId has been called with the 123 argument in Jasmine?
I know how to achieve that with Sinon, but I have no clue about Jasmine.
Where with Sinon you would do:
Sinon.spy(client, 'getLocationId');
...
Sinon.assert.calledWith(client.getLocationId, 123);
with Jasmine you do:
spyOn(client, 'getLocationId');
...
expect(client.getLocationId).toHaveBeenCalledWith(123);
Update: So, what you need is to mock the Client module when it's required by the module you're testing. I suggest using Proxyquire for this:
const proxyquire = require('proxyquire');
const mockedClientInstance = {
getLocationId: () => {}
};
const mockedClientConstructor = function() {
return mockedClientInstance;
};
const moduleToTest = proxyquire('moduleToTest.js', {
'./src/http/client': mockedClientConstructor
});
This will inject your mock as a dependency so that when the module you're testing requires ./src/http/client, it will get your mock instead of the real Client module. After this you just spy on the method in mockedClientInstance as normal:
spyOn(mockedClientInstance, 'getLocationId');
moduleToTest.handler();
expect(mockedClientInstance.getLocationId).toHaveBeenCalledWith(123);

Categories