Jest mockImplementationOnce is not overriding existing mock - javascript

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?

Related

Typescript/Javascript JEST spyOn not working (with Example)

I don't know why but my code does not work. Somehow the mock is not working and I get the errors commented on the code.
Any idea what the issue is?
import * as app from './app';
const cars = []; // some car objects
describe('Test', function() {
it('test1', async() => {
const spy = jest.spyOn(app, 'method1');
spy.mockReturnValue(Promise.resolve(cars));
const response = await app.main();
expect(spy).toHaveBeenCalled(); // Expected number of calls: >= 1, GOT 0
});
});
// app.ts
export const method1 = async() => {
// some stuff
}
export const main = async() => {
const cars: Car[] = await method1();
console.log(cars); // undefined
}
The method1 needs to be in separate file. If both methods are in the same file then mocking won't work.
So your setup should be like:
appUtils
export const method1 = async() => {
// some stuff
}
app
import { method1 } from './appUtils';
export const main = async() => {
const cars: Car[] = await method1();
console.log(cars); // undefined
}
the test
import { main } from '../app';
import * as appUtils from '../appUtils';
const cars = []; // some car objects
describe('it should work', () => {
it('should work', async () => {
const spy = jest.spyOn(appUtils, 'method1');
spy.mockResolvedValue(cars);
await main();
expect(spy).toHaveBeenCalled();
});
});

In Jest, how do I cause a function called within the function to return a specific value

This is the function I am testing (stripped down for simplicity's sake):
populate.js->
const { createSessionID } = require('./populate-template-utilities');
const createFile = async () => {
const responseHeader = {};
responseHeader.SessionID = createSessionID();
return responseHeader;
};
module.exports = {
createFile,
};
The function this function calls:
populate-template-utilities ->
const createSessionID = () => {
const digits = (Math.floor(Math.random() * 9000000000) + 1000000000).toString();
return `PAX${digits}`;
};
module.exports = {
createSessionID,
};
And my unit test (again stripped down):
const { createSessionID } = require('../app/lib/populate-template-utilities');
describe('create XML for output files', () => {
const mockID = jest
.spyOn(createSessionID)
.mockImplementation(() => 'PAX123456');
it('should create a PAX File', async () => {
const result = await createFile();
expect(result).toEqual(getFile);
});
});
I want createSessionID to return 'PAX123456' and think mockID should do it, but it's erroring with:
Cannot spy the undefined property because it is not a function; undefined given instead
The spyOn method needs at least two parameters: object and methodName.
Try sth like this:
import * as populateTemplateUtils from "../sessionStuff";
import { createFile } from "../createFile";
describe('create XML for output files', () => {
it('should create a PAX File', async () => {
jest
.spyOn(populateTemplateUtils, 'createSessionID')
.mockReturnValue('PAX123456');
const result = await createFile();
expect(result).toEqual({"SessionID": "PAX123456"});
});
});
It all started to work when I changed the:
module.exports = {
createSessionID,
};
to:
export const createSessionID = () => {

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

Problem with testing async functions in Angular with Jasmine

I am unit testing my Ionic 4 app with Jasmine. At the moment I am getting errors when running almost all my tests because I am doing something wrong with the async/await functions. The error that I get is: "Error: Timeout - Async function did not complete within 5000ms." I have changed this timeout_interval to another larger number and I still get this error.
My code is:
beforeEach(async(() => {
const storage = new Storage({
// Define Storage
name: '__mydb',
driverOrder: ['indexeddb', 'sqlite', 'websql']
});
component = new HomepagePage(storage);
TestBed.configureTestingModule({
declarations: [ HomepagePage ],
imports: [
IonicModule.forRoot(),
RouterTestingModule,
IonicStorageModule.forRoot()
]
}).compileComponents();
fixture = TestBed.createComponent(HomepagePage);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', async () => {
let home = jasmine.createSpyObj('home_spy',['getprice'])
const result = await home.getprice
expect(component).toBeTruthy();
});
describe('before logged in', () => {
let home = jasmine.createSpyObj('home_spy',['getprice'])
it('shows the price', async () => {
const result = await home.getprice
expect(home.getprice.length).toEqual(2);
});
});
My app is working fine when I am using it. However, could it be that the error is in the code itself? An example of the getprice() function is:
async getprice() {
var price_eth :number
var price_btc :number
try {
var url_btc = "https://api.binance.com/api/v1/klines?symbol=BTCUSDT&interval=1m";
var url_eth = "https://api.binance.com/api/v1/klines?symbol=ETHUSDT&interval=1m"
const btc = await axios.get(url_btc)
const eth = await axios.get(url_eth)
if (btc.data.length != 0 && eth.data.length != 0) {
this.loaded = 'yes'
} else {
this.loaded='no'
}
this.time = new Date(btc.data[btc.data.length-1][0]).toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1");
price_btc = Math.floor(btc.data[btc.data.length-1][1])
price_eth = Math.floor(eth.data[eth.data.length-1][1])
//These global variables are declared so they display the price but they are not used for any other functions.
this.glob_price_btc = price_btc
this.glob_price_eth = price_eth
return {
'price_btc':price_btc,'price_eth':price_eth
}
} catch {
this.loaded = 'no';
return
}
}
I think you misunderstood the function beforeEach(). beforeEach() should be used to setup the test environment and will be called before an unittest will be exeuted.
Here is an example how it would be more correct and to setup the timeout duration.
describe("xyz test suite", () => {
//called before every unittest
beforeEach(function() {
//Do setup stuff
});
it('shows the price', async () => {
const result = await getSomething()
expect(result).toEqual(2);
});
}, 10000 <- Timeout in milliseconds)

Trouble mocking a function in a module

I'm trying to create a simple test for a module. I got some firestore triggers in a module (see the module file down below). In the onDelete trigger I want to just test to see if the deleteColletion is called. To do that I need to mock out just the deleteCollection function. In my test (see onDelete should also delete the sub-collections trails and downloads in the test file) I mock the deleteCollection function and call the firestore trigger and checks if deleteCollection is called. This is the failing response I get from the test:
Error: expect(jest.fn()).toBeCalled()
Expected number of calls: >= 1
Received number of calls: 0
It seems like jest don't match one the function I mock. What am I doing wrong?
NB! I now that this test in it self is not a good test ;)
Test file
const functions = require('firebase-functions-test');
const admin = require('firebase-admin');
const triggers = require("../../data/protocol/triggers");
const {createEvent} = require("../test_utilities");
const testEnv = functions();
const mockUpdate = jest.fn();
mockUpdate.mockReturnValue(true);
jest.mock("firebase-admin", () => ({
initializeApp: jest.fn(),
firestore: () => ({
batch: jest.fn(() => ({commit: jest.fn()})),
collection: () => (
{
doc: () => ({update: mockUpdate}),
orderBy: () => ({
limit: jest.fn(() => ({
get: jest.fn(() => ({
size: 0,
docs: {
forEach: jest.fn(),
}
}))
}))
})
}
)
}),
})
);
jest.mock('../../data/protocol/triggers', () => ({
...(jest.requireActual('../../data/protocol/triggers')),
deleteCollection: jest.fn(() => [])
})
);
describe("Protocol trigger", () => {
let adminStub, triggersStub, api;
const context = {
params: {
protocolId: 0,
}
};
beforeAll(() => {
adminStub = jest.spyOn(admin, "initializeApp");
//triggersStub = jest.spyOn(triggers, 'deleteCollection');
api = require("../../index");
});
beforeEach(() => jest.clearAllMocks());
afterAll(() => {
adminStub.mockRestore();
//triggersStub.mockRestore();
testEnv.cleanup();
});
...
it('`onDelete` should also delete the sub-collections `trails` and `downloads`', async () =>
{
const onDeleteProtocol = testEnv.wrap(api.onDeleteProtocol);
const event = {id: 0};
await onDeleteProtocol(event, {});
expect(triggers.deleteCollection).toBeCalled();
expect(onDeleteProtocol(event, {})).toBe([]);
});
});
Module
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {lastModifiedNeedsUpdate} = require("../utilities/event");
function deleteCollection(db, collectionPath, batchSize) {
return deleteQueryBatch(db, db.collection(collectionPath).orderBy('__name__').limit(batchSize), batchSize, []);
}
...
const deleteProtocol = () => functions.firestore
.document('protocols/{protocolId}')
.onDelete((event, context) => {
return deleteCollection(admin.firestore(), `protocols/${event.id}/trails`, 1);
});
module.exports = {
deleteProtocol,
createProtocol,
updateProtocol,
deleteCollection,
};
-- Frode
I resolved this by moving the helper functions (deleteCollection and deleteQueryBatch) into it's own module and mock that module.
--
Frode

Categories

Resources