Mocking an imported module in Jest - javascript

I'm trying to figure out how to mock a constant (DEFAuLT_OPTIONS) with Jest in my test file
I have the following file structure:
src/
Builder/
- Constants.js
- Builder.js
- Builder.test.js
Files
// Constants.js
// Define a set of defaults to be used in my production build
const DEFAULT_OPTIONS = {
enableTransparency: true,
threshold: 100
};
export {
DEFAULT_OPTIONS
}
// Builder.js
// Exports a `buildTree` function, which uses the constants
import { DEFAULT_OPTIONS } from './Constants';
function buildTree(options) {
return {
root: '/',
options: { ...options, ...DEFAULT_OPTIONS }
};
}
export { buildTree };
// Builder.test.js
import { DEFAULT_OPTIONS } from './Constants';
import { buildTree } from './Builder';
describe('Builder', () => {
it('some description', () => {
// How to mock value of `DEFAULT_OPTIONS` here so that
// the call to `buildGTree` uses my mocked version?
const options = { foo: 'bar' };
const result = buildTree(options);
});
});
How can I -
Mock the value of DEFAULT_OPTIONS for a single test?
Mock the value of DEFAULT_OPTIONS for a suite of tests? (If different)
Thanks!
EDIT: I tried the following but the module seems to have a value of undefined
const mockDefaultOptions = {
optionA: 'a',
optionB: 'b'
}
jest.mock('./Constants', () => ({
DEFAULT_OPTIONS: mockDefaultOptions,
}));

You don't really need jest.mock as it's just a constant.
You can:
// import constants
import Constants from './Constants'
// modify DEFAULT_OPTIONS' value
Constants.DEFAULT_OPTIONS = {
threshold: 444
}
// then invoke your function
const result = buildTree(options);
and this is how you can modify it for a suit of tests
import { buildTree } from "./Builder";
describe("Builder", () => {
describe.each([333, 444, 555])(
"with DEFAULT_OPTIONS.threshhold = %d",
(threshold) => {
describe("buildTree", () => {
const Constants = require("./Constants");
const options = { foo: "bar" };
let result;
beforeAll(() => {
Constants.DEFAULT_OPTIONS = {
threshold,
};
result = buildTree(options);
});
it("some description", () => {
expect(result).toHaveProperty("options", {
...options,
threshold,
});
});
});
}
);
});

Related

How to clear/reset mocks in Vitest

I have a simple composable useRoles which I need to test
import { computed } from "vue";
import { useStore } from "./store";
export default function useRoles() {
const store = useStore();
const isLearner = computed(() => store.state.profile.currentRole === "learner");
return {
isLearner
};
}
My approach of testing it is the following
import { afterEach, expect } from "vitest";
import useRoles from "./useRoles";
describe("useRoles", () => {
afterEach(() => {
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should verify values when is:Learner", () => { // works
vi.mock("./store", () => ({
useStore: () => ({
state: {
profile: {
currentRole: "learner"
},
},
}),
}));
const { isLearner } = useRoles();
expect(isLearner.value).toBeTruthy();
});
it("should verify values when is:!Learner", () => { //fails
vi.mock("./store", () => ({
useStore: () => ({
state: {
profile: {
currentRole: "admin"
},
},
}),
}));
const { isLearner } = useRoles(); // Values are from prev mock
expect(isLearner.value).toBeFalsy();
});
});
And useStore is just a simple function that I intended to mock
export function useStore() {
return {/**/};
}
The first test runs successfully, it has all the mock values I implemented but the problem is that it's not resetting for each test (not resetting at all). The second test has the old values from the previous mock.
I have used
vi.clearAllMocks();
vi.resetAllMocks();
but for some reason clear or reset is not happening.
How can I clear vi.mock value for each test?
Solution
As it turned out I should not be called vi.mock multiple times. That was the main mistake
Substitutes all imported modules from provided path with another module. You can use configured Vite aliases inside a path. The call to vi.mock is hoisted, so it doesn't matter where you call it. It will always be executed before all imports.
Vitest statically analyzes your files to hoist vi.mock. It means that you cannot use vi that was not imported directly from vitest package (for example, from some utility file)
Docs
My fixed solution is below.
import useRoles from "./useRoles";
import { useStore } from "./store"; // Required the mock to work
vi.mock("./store");
describe("useRoles", () => {
afterEach(() => {
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should verify values when is:Learner", () => {
// #ts-ignore it is a mocked instance so we can use any vitest methods
useStore.mockReturnValue({
state: {
profile: {
currentRole: "learner",
},
},
});
const { isLearner } = useRoles();
expect(isLearner.value).toBeTruthy();
});
it("should verify values when is:!Learner", () => {
// You need to use either #ts-ignore or typecast it
// as following (<MockedFunction<typeof useStore>>useStore)
// since original function has different type but vitest mock transformed it
(<MockedFunction<typeof useStore>>useStore).mockReturnValue({
state: {
profile: {
currentRole: "admin",
},
},
});
const { isLearner } = useRoles();
expect(isLearner.value).toBeFalsy();
});
});
vitest = v0.23.0
I ran into the same issue and was able to find a workaround. In your case it should look like this:
import { afterEach, expect } from "vitest";
import useRoles from "./useRoles";
vi.mock("./store");
describe("useRoles", () => {
it("should verify values when is:Learner", async () => {
const store = await import("./store");
store.useStore = await vi.fn().mockReturnValueOnce({
useStore: () => ({
state: {
profile: {
currentRole: "learner"
},
},
}),
});
const { isLearner } = useRoles();
expect(isLearner.value).toBeTruthy();
});
it("should verify values when is:!Learner", async () => {
const store = await import("./store");
store.useStore = await vi.fn().mockReturnValueOnce({
useStore: () => ({
state: {
profile: {
currentRole: "admin"
},
},
}),
});
const { isLearner } = useRoles(); // Value is from current mock
expect(isLearner.value).toBeFalsy();
});
});

Jest mockImplementationOnce is not overriding existing mock

I have a common mock module as below:
// File: /<rootDir>/utils/__mocks__/mock-foo.js
const foo = require('foo');
jest.mock('foo');
foo.myFunction = jest.fn(() => ({
value: 10
}
));
In the jest.config.js I have below line, to make above mock available in all tests.
const config = {
setupFilesAfterEnv: [
'<rootDir>/utils/__mocks__/mock-foo.js'
]
}
Below is the unit test for myFlow.js file which is using Foo.js.
File: /<rootDir>/__test__/myFlow.js
const myFlow = require('../myFlow');
const foo = require('../foo');
describe('my unit tests', () => {
beforeAll(async () => {
jest.clearAllMocks();
});
it('test my function', async () => {
// This is not working, I always get value as 10
foo.myFunction.mockImplementationOnce(() => ({
value: 20,
}));
const result = await myFlow.someFunction();
expect(result).toBeTruthy();
});
});
How can I override the existing mock with jest mockImplementationOnce?

How to access a mock method returned from a mocked library

I'm mocking the #elastic/elasticsearch library and I want to test that the search method is called with the right arguments but I'm having issues accessing search from my tests.
In my ES mock I just export an object that includes a Client prop that returns another object that has the search prop. This is the way search is accessed from the library
const { Client } = require('#elastic/elasticsearch')
const client = new Client(...)
client.search(...)
__mocks__/#elastic/elasticsearch
module.exports = {
Client: jest.fn().mockImplementation(() => {
return {
search: (obj, cb) => {
return cb(
'',
{
statusCode: 200,
body: {
hits: {
hits: [
{
_source: esIndexes[obj.index]
}
]
}
}
}
)
}
}
})
}
__tests__/getAddresses.test.js
const { getAddresses } = require('../src/multiAddressLookup/utils/getAddresses')
const { Client } = require('#elastic/elasticsearch')
beforeEach(() => {
process.env.ES_CLUSTER_INDEX = 'foo'
process.env.ui = '*'
})
describe('multiAddressLookup', () => {
test('Should return the correct premises data with only the relevant "forecasted_outages"', async () => {
const event = {
foo: 'bar'
}
const esQueryResponse = {
"body": "\"foo\":\"bar\"",
"headers": {"Access-Control-Allow-Origin": '*'},
"statusCode": 200
}
await expect(getAddresses(event)).resolves.toEqual(esQueryResponse)
expect(Client().search).toHaveBeenCalled() // This fails with 0 calls registered
})
})
I'm not sure of any exact documentation for this scenario but I got the idea while looking through the Jest: The 4 ways to create an ES6 Mock Class - Automatic mock portion of the Jest documentation.
First, the search method in the ES mock, __mocks__/#elastic/elasticsearch, needs to be converted into a jest mock function, jest.fn(). Doing this gives us access to properties and values that jest mocks provide.
__mocks__/#elastic/elasticsearch.js converted
module.exports = {
Client: jest.fn().mockImplementation(() => {
return {
search: jest.fn((obj, cb) => {
return cb(
'',
{
statusCode: 200,
body: {
hits: {
hits: [
{
_source: esIndexes[obj.index]
}
]
}
}
}
)
})
}
})
}
Second, in our tests we need to follow the path from the Client mock class until we find out methods. The syntax is MockClass.mock.results[0].value.mockFunction.
Example Test
const { Client } = require('#elastic/elasticsearch') // This is located in the "__mocks__" folder in the root of your project
const { getAddresses } = require('../../src/getAddresses') // This is the file we wrote and what we are unit testing
describe('getAddresses', () => {
it('Should call the ES Search method', async () => {
const event = { ... }
const expected = { ... }
await expect(getAddresses(event)).resolves.toEqual(expected) // pass
expect(Client.mock.results[0].value.search).toHaveBeenCalled() // pass
})
})

How to check test case response using jasemine?

I am writing test case for API where I have passed params and main file method is calling http request to get the data, So core() is basically calling the API and getting the response. This is just backend code that we have to test using jasemine.
First how to see response in test case if that's matched with success that is defined in test.
What is a correct way to write test in below scenario.
balance.spec.ts
import { GetAccountBalance } from './GetAccountBalance.node'
import { Promise } from 'es6-promise'
import { HttpRequest } from '../../../core/http/HttpRequest.node'
import * as sinon from 'sinon'
import { getValidationError } from '../../../common/ValidationErrorFactory'
// for easy mocking and cleanup
const sandbox = sinon.createSandbox()
afterAll(function afterTests () {
sandbox.restore()
})
describe('getAccountBalance', function () {
// the module under test
const module = new GetAccountBalance()
const testURL = 'https://essdd.max.com:4535'
const urlPath = '/webServices/instrument/accountBalance'
const fullURL = testURL + urlPath
const options = { isJSON: true }
let result
const stubbedEC = sandbox.spy(getValidationError)
const stubbedHttp = sandbox.createStubInstance(HttpRequest)
const success = {
header: {
serviceName: 'accountBalance',
statusCode: '0000',
statusDesc: 'SUCCESS',
},
response: {
balanceAccount: '6346.44',
},
}
const params = { Id: '21544', appName: 'instrucn', channelName: 'Web' }
describe('core() is called with correct params', function () {
beforeEach(function () {
result = module.core(params, stubbedHttp)
})
it('it should return the response from server', function () {
// Invoke the unit being tested as necessary
result.then((data) => {
expect(data).toBe(success)
})
})
})
})
getaccountBalnce.ts
public core(args: IAccountBalanceParam, httpRequest: HttpRequestBase): Promise<any> {
console.log('enter core');
const DBPLurl: string = this.constructDBPLurl(args);
const DBPLrequest: IAccountBalanceDBPLParam = this.constructDBPLrequest(args);
return Promise.resolve(httpRequest.makeRequest(DBPLurl, DBPLrequest, {isJSON: true}));
}

How to use mockDOMSource to test a stream of actions in Cycle.js?

I realize there is probably a better way using cycle/time, but I'm just trying to understand the basics. Somehow, my action$ stream doesn't seem to be running; I've tried to construct multiple mock doms using xs.periodic. The test framework is mocha.
import 'mocha';
import {expect} from 'chai';
import xs from 'xstream';
import Stream from 'xstream';
import {mockDOMSource, DOMSource} from '#cycle/dom';
import {HTTPSource} from '#cycle/http';
import XStreamAdapter from '#cycle/xstream-adapter';
export interface Props {
displayAbs: boolean
}
export interface ISources {
DOM: DOMSource;
http: HTTPSource;
}
function testIntent(sources: ISources):Stream<Props> {
return xs.merge<Props>(
sources.DOM
.select('.absShow').events('click')
.mapTo( { displayAbs: true } ),
sources.DOM
.select('.absHide').events('click')
.mapTo( { displayAbs: false } )
).startWith( {displayAbs: false } );
}
describe( 'Test', ()=>{
describe( 'intent()', ()=>{
it('should change on click to shows and hides', () => {
let listenerGotEnd = false;
const mDOM$: Stream<DOMSource> = xs.periodic(1000).take(6).map(ii => {
if (ii % 2 == 0) {
return mockDOMSource(XStreamAdapter, {
'.absShow': {'click': xs.of({target: {}})}
})
}
else {
return mockDOMSource(XStreamAdapter, {
'.absHide': {'click': xs.of({target: {}})}
})
}
});
const action$ = mDOM$.map(mDOM => testIntent({
DOM: mDOM,
http: {} as HTTPSource,
})).flatten();
action$.addListener({
next: (x) => {
console.log("x is " + x.displayAbs);
},
error: (err) => {
console.log("error is:" + err);
throw err;
},
complete: () => { listenerGotEnd = true; }
});
expect(listenerGotEnd).to.equal(true);
});
});/* end of describe intent */
});
The primary reason the test is not running is because it's asynchronous, so in mocha we need to take in the done callback and then call it when our test is done.
Without using #cycle/time, this is how I would write this test:
import 'mocha';
import {expect} from 'chai';
import xs, {Stream} from 'xstream';
import {mockDOMSource, DOMSource} from '#cycle/dom';
import XStreamAdapter from '#cycle/xstream-adapter';
export interface Props {
displayAbs: boolean
}
export interface ISources {
DOM: DOMSource;
}
function testIntent(sources: ISources):Stream<Props> {
return xs.merge<Props>(
sources.DOM
.select('.absShow').events('click')
.mapTo( { displayAbs: true } ),
sources.DOM
.select('.absHide').events('click')
.mapTo( { displayAbs: false } )
).startWith( {displayAbs: false } );
}
describe('Test', () => {
describe('intent()', () => {
it('should change on click to shows and hides', (done) => {
const show$ = xs.create();
const hide$ = xs.create();
const DOM = mockDOMSource(XStreamAdapter, {
'.absShow': {
'click': show$
},
'.absHide': {
'click': hide$
}
});
const intent$ = testIntent({DOM});
const expectedValues = [
{displayAbs: false},
{displayAbs: true},
{displayAbs: false},
]
intent$.take(expectedValues.length).addListener({
next: (x) => {
expect(x).to.deep.equal(expectedValues.shift());
},
error: done,
complete: done
});
show$.shamefullySendNext({});
hide$.shamefullySendNext({});
});
});
});
This test runs in 11ms, which is a fair bit faster than using xs.periodic(1000).take(6)
For comparison, here is how I would write it with #cycle/time:
import {mockTimeSource} from '#cycle/time'
describe('Test', () => {
describe('intent()', () => {
it('should change on click to shows and hides', (done) => {
const Time = mockTimeSource();
const show$ = Time.diagram('---x-----');
const hide$ = Time.diagram('------x--');
const expected$ = Time.diagram('f--t--f--', {f: false, t: true});
const DOM = mockDOMSource({
'.absShow': {
'click': show$
},
'.absHide': {
'click': hide$
}
});
const intent$ = testIntent({DOM}).map(intent => intent.displayAbs);
Time.assertEqual(intent$, expected$);
Time.run(done);
});
});
});
The first version is effectively what #cycle/time is doing for you under the hood, this is just a slightly nicer way of writing it. It's also nice to have better error messages.

Categories

Resources