Why my stub Sinon.Stub is ignored? - javascript

I'm wondering why my stub is ignored.
Let say I have a file called myfile.ts which exports two async methods. A and B.
import { sendResult } from '../index'
export async A(): Promise<string[]> {
// making an async api call
const result = await apiCall()
// do sync treatement on result then send it back
...
return value
}
export async B(): Promise<void> {
// Calling A()
const aResult = await A()
// do some sync treatement and call a method from an other module than
// returns a Promise<void>
...
sendResult()
}
I need to unit test my B method and stub A and sendResult
My testFile.ts looks like
import { sandbox } from 'sinon'
import * as AB from './modules/ab'
import * as INDX from './index'
const testSandbox = sandbox.create()
describe('my tests', function () {
beforeEach(() => {
testSandbox.stub(INDX, 'sendResult').resolves()
testSandbox.stub(AB, 'A').resolves([])
})
afterEach(() => {
testSandbox.restore()
})
it('should pass but it is not', async function (done) {
await AB.B()
const sendResultStub = UL.sendResult as SinonStub
assert.calledOnce(sendResultStub)
done()
})
})
I do not understand why sendResult is well stubed but A is not. What do I miss?
Thanks is advances folks!
Bastien

Related

How can I mock a class using jest?

How can I mock something to test something like the following codes. I tried to follow this official doc, but still not working for me https://jestjs.io/docs/es6-class-mocks#calling-jestmock-with-the-module-factory-parameter
// somefile.ts
export const myPublish = async (event: any, context: any): Promise<any> => {
const myExportHelper = await ExportHelper.getInstance({
...commonProps,
});
// just some other stuff
// just some other stuff
await myExportHelper.transfer(arg1, arg2);
};
export class ExportHelper {
constructor(
private readonly bucket: string,
private readonly read: AWS.S3,
private readonly write: AWS.S3
) {}
static async getInstance(props: {
param1: string;
}) {
...
...
return new ExportHelper(arg1, arg2, arg3);
};
async transfer(param1, param2) {
...
...
console.log('bla bla bla');
}
}
// testfile.test.ts
import { myPublish, ExportHelper } from '../somefile';
beforeEach(() => {
});
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('myTest', () => {
it('should run successfully', async () => {
// Arrange
const eventMock = {
Records: [
{
...
}
]
}
jest.mock('../somefile');
const mockActualExportHelper = jest.requireActual('../somefile').ExportHelper;
const mockGetInstanceImpl = () => {};
// this says cannot read property instances of undefined
const mockExportHelper = mockActualExportHelper.mock.instances[0];
mockExportHelper.getInstance.mockImplementation(mockGetInstanceImpl);
mockExportHelper.transfer.mockImplementation(mockGetInstanceImpl);
// Act
await myPublish(eventMock, jasmine.any({}));
// Assert
expect(ExportHelper.getInstance).toBeCalled();
expect(ExportHelper.transfer).toBeCalled(); // also not sure if this is valid to use ExportHelper
});
});
I think what you're looking for is not a mock. If you want to spy what functions are called, you will need to use the spyOn. In jest you can do the following:
jest.spyOn(MyClass, 'myMethod');
And you can also mock the implementation to subtitute the default behavior of a method, a generalist example can be like this:
jest.spyOn(MyClass, 'myMethod').mockImplementation(jest.fn());
With that said, I would rewrite the test to spy the methods from ExportHelper and avoid external calls:
import {ExportHelper, myPublish} from '../app';
beforeEach(() => {
});
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('myTest', () => {
it('should run successfully', async () => {
// Arrange
const eventMock = {
Records: [
{
// ...
}
]
}
jest.spyOn(ExportHelper, 'getInstance').mockImplementation(jest.fn());
jest.spyOn(ExportHelper, 'transfer').mockImplementation(jest.fn());
// Act
await myPublish('arg1', 'arg2');
// Assert
expect(ExportHelper.getInstance).toBeCalled();
expect(ExportHelper.transfer).toBeCalled();
});
});
I just replaced this piece of code:
jest.mock('../somefile');
const mockActualExportHelper = jest.requireActual('../somefile').ExportHelper;
const mockGetInstanceImpl = () => {};
// this says cannot read property instances of undefined
const mockExportHelper = mockActualExportHelper.mock.instances[0];
mockExportHelper.getInstance.mockImplementation(mockGetInstanceImpl);
mockExportHelper.transfer.mockImplementation(mockGetInstanceImpl);
with
jest.spyOn(ExportHelper, 'getInstance').mockImplementation(jest.fn());
jest.spyOn(ExportHelper, 'transfer').mockImplementation(jest.fn());
Because jest will track down and watch any of these method's calls and then we can use jest's matchers to test if both of them were called. And the mockImplementation will isolate any further calls to be maded.
One thing that I noticed while reproducing your example, is that the transfer method is not being treated as a method when you get the instance from getInstance and therefore, the tests will not pass. But I think this question is not in the scope of the topic. Dunno if just happens to me.

How to test an imported function returning a Promise in Jest?

I have this very simple function, and I must write a test. The goal is to fulfill the coverage threshold.
import { lambdaPromise } from '#helpers';
export const main = async event => lambdaPromise(event, findUsers);
The lambdaPromise() function returns a Promise. I am trying to mock it, then tell if it was called. Here's what I have:
import { main, findUsers } from '../src/lambdas/user/findUsers';
import { lambdaPromise } from '#helpers';
const mockEvent = {
arguments: {
userDataQuery: {
email: 'johndoe#whatever.com'
}
}
};
const mockLambdaPromise = jest.fn();
jest.mock('#helpers', () => ({
lambdaPromise: jest.fn().mockImplementation(() => mockLambdaPromise)
}));
describe('findUsers', () => {
it('should have a main function', async () => {
const mockPromise = main(mockEvent);
expect(mockPromise).toBeInstanceOf(Promise);
expect(mockLambdaPromise).toBeCalledWith(mockEvent, findUsers);
});
});
Now mockLambdaPromise never gets called. How to fix that?
Your mock returns a function, but you didn't call that function. The following makes it pass.
jest.mock("./helpers", () => ({
lambdaPromise: jest
.fn()
.mockImplementation((a, b) => mockLambdaPromise(a, b)),
}));
The complexity of that mock can be reduced by just mocking the resolved value with a spy:
import { main, findUsers } from "./findUsers";
import * as helpers from "./helpers";
describe("findUsers", () => {
it("should have a main function", async () => {
const spy = jest.spyOn(helpers, "lambdaPromise").mockResolvedValue();
await main(mockEvent);
expect(spy).toBeCalledWith(mockEvent, findUsers);
});
});

How to stub exported function from module in cypress?

I'm not able to see stubbed response for getName method while invoking getUser from Cypress. Is there a way to correct this?
// Service.ts
export const getName = (): string => {
return name;
}
// User.ts
import {getName} from './Service'
export const getUser = (): User => {
const name = getName();
// ... rest of code for User creation
}
// User.cypress.ts
import * as service from './Service'
it('tests user', () => {
cy.stub(service, 'getName').returns('abc');
cy.get('#get-user-id').click();
});
You may need to change the way the function is exported from Service.ts.
Try adding a default export to the module.
// Service.ts
const getName = (): string => {
return name;
}
module.exports {
getName
}
// User.cypress.ts
import service from './Service'
it('tests user', () => {
cy.stub(service, 'getName').returns('abc');
cy.get('#get-user-id').click();
});

Jest check when async function gets called

I'm trying to test whether an async function (fire and forget) gets called.
Content.js
export async function fireAndForgetFunction() {
...
}
export async function getData() {
...
fireAndForgetFunction()
return true;
}
I would like to test if fireAndForgetFunction has been called more than once.
Current test
import * as ContentFetch from '../Content';
const { getData } = ContentFetch;
const { fireAndForgetFunction } = ContentFetch;
it('test',async () => {
const spy = jest.spyOn(ContentFetch, 'fireAndForgetFunction');
await getData();
expect(spy).toHaveBeenCalled();
})
The test result to an error saying
Expected number of calls: >= 1
Received number of calls: 0
How could I do this test?
If you don't want to await for fireAndForgetFunction in getData(), which I assume is the case, then providing a mock implementation of fireAndForgetFunction when creating the spy is your best option:
it('test', (done) => {
const spy = jest.spyOn(ContentFetch, 'fireAndForgetFunction')
.mockImplementation(() => {
expect(spy).toHaveBeenCalled();
done();
})
getData();
})

How to test async function with spyOn?

I am trying to test an async function in a react native app.
class myClass extends React.Component {
...
closeModal = async () => {
if (someCondition) {
await myFunction1();
} else {
await myFunction2();
}
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('Main');
};
...
}
This is my test:
const navigation = {
navigate: jest.fn(),
state: { params: { onGoBack: jest.fn() } },
};
const renderComponent = overrides => {
props = {
navigation,
...overrides,
};
return shallow(< myClass.wrappedComponent {...props} />);
};
describe('When the user presses the close icon', () => {
it('should close the modal', () => {
const wrapper = renderComponent();
const instance = wrapper.instance();
const spyCloseModal = jest.spyOn(instance, 'closeModal');
instance().forceUpdate();
component
.find({ testID: 'close-icon' })
.props()
.onPress();
expect(spyCloseModal).toHaveBeenCalled(); // this is passed
expect(navigation.navigate).toHaveBeenCalled(); // this is not passed
});
});
It looks like it gets stuck on the await calls. If I remove the await calls then it passes. Someone mentioned in another post to use .and.callThrough after spyOn but it gives me this error
Cannot read property 'callThrough' of undefined
one of solution is to make your test async and run await (anything) to split your test into several microtasks:
it('should close the modal', async () => {
const wrapper = renderComponent();
component
.find({ testID: 'close-icon' })
.props()
.onPress();
await Promise.resolve();
expect(navigation.state.params.onGoBack).toHaveBeenCalled();
expect(navigation.navigate).toHaveBeenCalledWith("Main");
});
I believe you don't need either .forceUpdate nor .spyOn on instance method. once navigation happens properly it does not matter by what internal method it has been called
more on microtask vs macrotask: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f
alternative is to use macrotask(setTimeout(...., 0))
it('should close the modal', (done) => {
const wrapper = renderComponent();
component
.find({ testID: 'close-icon' })
.props()
.onPress();
setTimeout(() => {
expect(navigation.state.params.onGoBack).toHaveBeenCalled();
expect(navigation.navigate).toHaveBeenCalledWith("Main");
done();
});
}
Yes, you're on the right track...the issue is that closeModal is asynchronous.
The await hasn't finished by the time execution returns to the test so this.props.navigation.navigate hasn't been called yet.
The test needs to wait for closeModal to complete before asserting that navigate has been called.
closeModal is an async function so it will return a Promise...
...and you can use the spy to retrieve the Promise it returns...
...then you can call await on that Promise in your test to make sure closeModal has completed before asserting that navigate has been called.
Here is a simplified working example to get you started:
import * as React from 'react';
import { shallow } from 'enzyme';
class MyClass extends React.Component {
closeModal = async () => {
await Promise.resolve();
this.props.navigation.navigate('Main');
}
render() { return <div onClick={() => this.closeModal()}></div> }
}
test('MyClass', async () => { // <= async test function
const props = { navigation: { navigate: jest.fn() }};
const wrapper = shallow(<MyClass {...props} />);
const instance = wrapper.instance();
const spyCloseModal = jest.spyOn(instance, 'closeModal');
wrapper.find('div').simulate('click');
expect(spyCloseModal).toHaveBeenCalled(); // Success!
const promise = spyCloseModal.mock.results[0].value; // <= get the Promise returned by closeModal
await promise; // <= await the Promise
expect(props.navigation.navigate).toHaveBeenCalled(); // Success!
})
Note the use of mockFn.mock.results to get the Promise returned by closeModal.

Categories

Resources