How to expect one function to call another function? - javascript

I am trying to mock a function call, and expect it to have called another function within it once.
myFunctions.test.js
import { resetModal } from '../myFunctions.js';
describe('resetModal', () => {
it('calls the clearSomethingInModal function', () => {
const clearSomethingInModal = jest.fn();
resetModal();
expect(clearSomethingInModal.mock.calls.length).toBe(1);
})
})
myFunctions.js
export resetModal() {
clearSomethingInModal()
}
However, Jest output says that it has not been called. How can I best do this?

Your approach does not work because you mock clearSomethingInModal only in the context of your test file, so clearSomethingInModal in the myFunctions.js is still the original. The main point is that you can't mock something that is directly created in myFunctions.js. The only thing that you can mock are:
Modules that you import to myFunctions.js, like import clearSomethingInModal from 'clearSomethingInModal';
Callbacks that you pass into your function when calling them from your test;
This makes sense if you think about myFunctions.js as a blackbox, where you can control what goes in, like imports or function arguments, and where you can test what comes out. But you can not test the stuff that happens inside the box.
Here are two example that reflect the 2 points in the list:
myFunctions.test.js
import { resetModal } from '../myFunctions.js';
import clearSomethingInModal from 'clearSomethingInModal';
jest.mock('clearSomethingInModal', () => jest.fn())
describe('resetModal', () => {
it('calls the clearSomethingInModal function', () => {
resetCreationModal();
expect(clearSomethingInModal.mock.calls.length).toBe(1);
})
})
myFunctions.js
import clearSomethingInModal from 'clearSomethingInModal';
export resetModal() {
clearSomethingInModal()
}
myFunctions.test.js
import { resetModal } from '../myFunctions.js';
describe('resetModal', () => {
it('calls the clearSomethingInModal function', () => {
const clearSomethingInModal = jest.fn();
resetCreationModal(clearSomethingInModal);
expect(clearSomethingInModal.mock.calls.length).toBe(1);
})
})
myFunctions.js
export resetModal(clearSomethingInModal) {
clearSomethingInModal()
}

Another way is to use done and mock or spy on the implementation of the last function and check if the previous function was called by then.
it('should call function2 after function1', (done) => {
expect.assertions(2)
function2.mockImplementationOnce(() => {
expect(function1).toHaveBeenCalled()
done()
})
act() // This is where you run function you are testing
})
The drawback of this solution is that the structure of the test is not
// arrange
// act
// assert
but rather
// arrange & assert
// act

Related

Mock only works for test file, but not when executing instance

I am trying to mock a method used by the class I'm testing. The method is located in the same file as the class I'm testing.
The tested file:
export function trackErrors() {
console.log("This SHOULD NOT BE SEEN ON tests");
// Currently this console log is visible hwne running tests
}
// Component that is tested
export const form = () => {
return <FormComponent callback={trackErrors} />
}
The test itself:
With mock
The jest.mock() is the first thing to happen, before the import.
jest.mock('./testedFile', () => {
const actualModule = jest.requireActual('./testedFile')
return new Proxy(actualModule, {
get: (target, property) => {
switch (property) {
case 'trackErrors': {
return jest.fn()
}
default: {
return target[property]
}
}
},
})
})
import * as SModule from "./testedFile";
it.only("Calls trackErrors", async () => {
const { getByTestId } = <SModule.form />;
await typeEmail(getByTestId, "abcd");
await waitFor(() => {
expect(SModule.trackErrors).toHaveBeenCalledTimes(1);
});
});
This does not work. When printing SModule.trackErrors, I can see the mocked function being put out. But when the test is executed, the test fails with 0 calls and I see this console.log This SHOULD NOT BE SEEN ON tests. That means that the method is mocked good inside the test, but when rendering the component it continues to call the prexistent method (the one not modked).
I've also tried the spyOn approach, but the outcome is exactly the same.

Mock function inside function with Jest

I would like to mock a function that is called inside another function and also control what it can return or possibly throw an error.
main.ts
import function from './function';
const main = async() => {
...
try {
value = await function(arguments);
} catch(error) {
...
}
}
For this dumb case for example if I wanted to test that "function" has been called with the right parameters, mocking its return value. I also want to be able to mock a thrown error from that function.
Basically I dont want my test to go inside this function, I just want to mock it and test the rest of the main function with the return value, and test the catch handler.
Here is the base of what I would want to do:
main.test.ts
import main from './main';
import function from './function';
describe('Test main', () => {
it('should call function with the right arguments', async() => {
function = jest.fn(() => value);
await main();
expect(function).toHaveBeenLastCalledWith(arguments);
// expect not to have thrown error
});
it('should handle the error of function', async() => {
function = jest.fn().mockRejectedValue(new Error('error'));
await main();
expect(function).toHaveBeenLastCalledWith(arguments);
// expect to have thrown error
});
});
Thank you for your help!
You need to mock the module.
Something like this:
import function from './function';
jest.mock("./function")
it("some", () => {
function.mockRejectedValue(new Error('error'));
//do stuff
expect(function).toHaveBeenLastCalledWith(arguments);
});

Jest testing context / spy on mocked variables created outside of functions (class level) Postmark

I'm trying to do some testing in Jest but getting stuck with a mock/spy. I've managed to get the test working but only by changing my implementation (which I feel dirty about).
Here's the test:
import * as postmark from 'postmark';
jest.mock('postmark');
const mockGetServers = jest.fn();
const AccountClient = jest.fn(() => {
return {
getServers: mockGetServers
};
});
postmark.AccountClient = AccountClient;
import accountApi from './account-api';
describe('account-api', () => {
describe('listServers', () => {
it('calls postmark listServers', async () => {
await accountApi.listServers();
expect(mockGetServers).toHaveBeenCalledTimes(1);
});
});
});
Here's the working implementation:
import * as postmark from 'postmark';
const accountToken = 'some-token-number';
const listServers = async () => {
try {
const accountClient = postmark.AccountClient(accountToken);
const servers = await accountClient.getServers();
return servers;
} catch (e) {
console.log('ERROR', e);
}
};
export default {
listServers
}
Here's the original implementation:
import * as postmark from 'postmark';
const accountToken = 'some-token-number';
const accountClient = postmark.AccountClient(accountToken);
const listServers = async () => {
try {
const servers = await accountClient.getServers();
return servers;
} catch (e) {
console.log('ERROR', e);
}
};
export default {
listServers
}
The only change is where in the code the accountClient is created (either inside or outside of the listServers function). The original implementation would complete and jest would report the mock hadn't been called.
I'm stumped as to why this doesn't work to start with and guessing it's something to do with context of the mock. Am I missing something about the way jest works under the hood? As the implementation of accountApi will have more functions all using the same client it makes sense to create one for all functions rather than per function. Creating it per function doesn't sit right with me.
What is different about the way I have created the accountClient that means the mock can be spied on in the test? Is there a way I can mock (and spy on) the object that is created at class level not at function level?
Thanks
Am I missing something about the way jest works under the hood?
Two things to note:
ES6 import calls are hoisted to the top of the current scope
babel-jest hoists calls to jest.mock to the top of their code block (above everything including any ES6 import calls in the block)
What is different about the way I have created the accountClient that means the mock can be spied on in the test?
In both cases this runs first:
jest.mock('postmark');
...which will auto-mock the postmark module.
Then this runs:
import accountApi from './account-api';
In the original implementation this line runs:
const accountClient = postmark.AccountClient(accountToken);
...which captures the result of calling postmark.AccountClient and saves it in accountClient. The auto-mock of postmark will have stubbed AccountClient with a mock function that returns undefined, so accountClient will be set to undefined.
In both cases the test code now starts running which sets up the mock for postmark.AccountClient.
Then during the test this line runs:
await accountApi.listServers();
In the original implementation that call ends up running this:
const servers = await accountClient.getServers();
...which drops to the catch since accountClient is undefined, the error is logged, and the test continues until it fails on this line:
expect(mockGetServers).toHaveBeenCalledTimes(1);
...since mockGetServers was never called.
On the other hand, in the working implementation this runs:
const accountClient = postmark.AccountClient(accountToken);
const servers = await accountClient.getServers();
...and since postmark is mocked by this point it uses the mock and the test passes.
Is there a way I can mock (and spy on) the object that is created at class level not at function level?
Yes.
Because the original implementation captures the result of calling postmark.AccountClient as soon as it is imported, you just have to make sure your mock is set up before you import the original implementation.
One of the easiest ways to do that is to set up your mock with a module factory during the call to jest.mock since it gets hoisted and runs first.
Here is an updated test that works with the original implementation:
import * as postmark from 'postmark';
jest.mock('postmark', () => { // use a module factory
const mockGetServers = jest.fn();
const AccountClient = jest.fn(() => {
return {
getServers: mockGetServers // NOTE: this returns the same mockGetServers every time
};
});
return {
AccountClient
}
});
import accountApi from './account-api';
describe('account-api', () => {
describe('listServers', () => {
it('calls postmark listServers', async () => {
await accountApi.listServers();
const mockGetServers = postmark.AccountClient().getServers; // get mockGetServers
expect(mockGetServers).toHaveBeenCalledTimes(1); // Success!
});
});
});
I think you might want to look at proxyquire.
import * as postmark from 'postmark';
import * as proxyquire from 'proxyquire';
jest.mock('postmark');
const mockGetServers = jest.fn();
const AccountClient = jest.fn(() => {
return {
getServers: mockGetServers
};
});
postmark.AccountClient = AccountClient;
import accountApi from proxyquire('./account-api', postmark);
describe('account-api', () => {
describe('listServers', () => {
it('calls postmark listServers', async () => {
await accountApi.listServers();
expect(mockGetServers).toHaveBeenCalledTimes(1);
});
});
});
Note that I have not tested this implementation; tweaking may be required.

How to mock const method in jest?

I unit test code in typescript, use jest. Please teach me how to mock getData to return the expected value. My code as below:
// File util.ts
export const getData = async () => {
// Todo something
return data;
}
// File execution.ts import { getData } from './util';
function execute()
{
// todo something
const data = await getData();
// todo something
}
The problem is that your function returns a promise. Depends on how you use it there are several ways to mock it.
The simplest way would be to mock it directly, but then it will always return the same value:
// note, the path is relative to your test file
jest.mock('./util', () => ({ getData: () => 'someValue' }));
If you want to test both the resolved and the rejected case you need to mock getData so it will return a spy where you later on can change the implementation use mockImplementation. You also need to use async/await to make the test work, have a look at the docs about asynchronous testing:
import { getData } from './util';
jest.mock('./util', () => ({ getData: ()=> jest.fn() }));
it('success case', async () => {
const result = Promise.resolve('someValue');
getData.mockImplementation(() => result);
// call your function to test
await result; // you need to use await to make jest aware of the promise
});
it('error case', async () => {
const result = Promise.reject(new Error('someError'));
getData.mockImplementation(() => result);
// call your function to test
await expect(result).rejects.toThrow('someError');
});
Try the following in your test file.
Import the function from the module.
import { getData } from './util';
Then mock the module with the function and its return value after all the import statements
jest.mock('./util', () => ({ getData: jest.fn() }))
getData.mockReturnValue("abc");
Then use it in your tests.
Because mocking expression functions can be a real pain to get right, I'm posting a full example below.
Scenario
Let's say we want to test some code that performs some REST call, but we don't want the actual REST call to be made:
// doWithApi.ts
export const doSomethingWithRest = () => {
post("some-url", 123);
}
Where the post is a function expression in a separate file:
// apiHelpers.ts
export const post = (url: string, num: number) => {
throw Error("I'm a REST call that should not run during unit tests!");
}
Setup
Since the post function is used directly (and not passed in as a parameter), we must create a mock file that Jest can use during tests as a replacement for the real post function:
// __mocks__/apiHelpers.ts
export const post = jest.fn();
Spy and Test
Now, finally inside the actual test, we may do the following:
// mockAndSpyInternals.test.ts
import {doSomethingWithRest} from "./doWithApi";
afterEach(jest.clearAllMocks); // Resets the spy between tests
jest.mock("./apiHelpers"); // Replaces runtime functions inside 'apiHelpers' with those found inside __mocks__. Path is relative to current file. Note that we reference the file we want to replace, not the mock we replace it with.
test("When doSomethingWithRest is called, a REST call is performed.", () => {
// If we want to spy on the post method to perform assertions we must add the following lines.
// If no spy is wanted, these lines can be omitted.
const apiHelpers = require("./apiHelpers");
const postSpy = jest.spyOn(apiHelpers, "post");
// Alter the spy if desired (e.g by mocking a resolved promise)
// postSpy.mockImplementation(() => Promise.resolve({..some object}))
doSomethingWithRest();
expect(postSpy).toBeCalledTimes(1)
expect(postSpy).toHaveBeenCalledWith("some-url", 123);
});
Examples are made using Jest 24.9.0 and Typescript 3.7.4

Cannot stub functions inside a module when using sinon and babel-plugin-rewire

In a mocha/chai setup, I'm trying to use babel-plugin-rewire in conjunction with sinon for testing and stubbing functions within the same module. These are the example files below:
Firstly, an index.js and test file that uses both sinon and babel-plugin-rewire. Rewiring works, but for some reason, my stubs don't work. The function it is applied to is never stubbed and only the original value is returned:
// index.js
function foo() {
return "foo";
}
export function bar() {
return foo();
}
export function jar() {
return "jar";
}
//index.test.js
import chai from "chai";
import sinon from "sinon";
import * as index from "./index";
const expect = chai.expect;
const sandbox = sinon.sandbox.create();
describe("babel-plugin-rewire", () => {
it("should be able to rewire", () => {
index.default.__set__("foo", () => {
return "rewired"; // successfullly rewires
});
expect(index.bar()).to.equal("rewired"); // works fine
index.default.__ResetDependency__("foo");
expect(index.bar()).to.equal("bar"); // works fine
});
});
describe("sinon", () => {
afterEach(() => {
sandbox.restore();
});
it("should call the original jar", () => {
expect(index.jar()).to.equal("jar"); // works fine
});
it("should call the stubbed jar", () => {
sandbox.stub(index, "jar").returns("stub");
expect(index.jar()).to.equal("stub"); // fails
});
});
And here is two example files solely using sinon stubs. The same thing happens:
// stub.js
export function stub() {
return "stub me";
}
// stub.test.js
import * as stub from "./stub";
import sinon from "sinon";
import chai from "chai";
const expect = chai.expect;
const sandbox = sinon.createSandbox();
const text = "I have been stubbed";
describe("sinon stubs", () => {
afterEach(() => {
sandbox.restore();
});
it("should stub", () => {
sandbox.stub(stub, "stub").returns(text);
expect(stub.stub()).to.equal(text); // fails
});
});
And this here is the babelrc that is being used for mocha
{
"presets": [
"#babel/preset-env"
],
"plugins": [
"rewire"
]
}
If I remove rewire from the plugins, the problem goes away. Though obviously this means I can't use rewire, which as I mentioned earlier I need in order to stub functions within the same dependency. Is this a bug with the module or am I missing something here?
I had the same problem with one of my tests. There was an action that after signing out the user, triggered a page redirect. The redirect itself was implemented in separate file.
import * as utils from '../utils.js';
import sinon from 'sinon';
it('will dispatch an action and redirect the user when singing out', () => {
const redirectStub = sinon.stub(utils, 'redirect').returns(true);
// dispatch and assertion of action omitted
expect(redirectStub.calledOnce).toBeTruthy();
redirectStub.restore();
});
Then, for various reasons, I had to add babel-rewire-plugin to my tests. This, broke that specific test above. The calledOnce or any other sinon methods were always returning false.
Unfortunately, I wasn't able to make the spying/stubbing work anymore. I had to rewrite my test like this:
import { __RewireAPI__ } from '../utils.js';
import sinon from 'sinon';
it('will dispatch an action and redirect the user when singing out', () => {
__RewireAPI__.__Rewire__('redirect', () => true);
// dispatch and assertion of action omitted
__RewireAPI__.ResetDependencty('redirect');
});
As you can see, there are no spies or stubs assertions anymore. I rewired the redirect method and then reset it.
There is a babel-plugin-rewire-exports library however, that seems to allow you to both rewire and spy/stub. I haven't tried it myself, but it can be an option if you don't want to rewrite your tests. Here is the link.

Categories