Sinon stub not working on exported function - javascript

I trying to stub a function which reloads a webpage.
export function reloadPage() {
window.location.reload();
}
This is how I am stubbing the function:
import * as ref from 'file/where/reloadpage/is/defined';
describe('...', function () {
...
before(function () {
this.reloadStub = sinon.stub(ref, 'reloadPage');
});
});
It is still not stubbing the method properly. My tests throw a "full page reload" error.
I don't know what I am doing wrong.

Here is a working example:
index.ts:
export function reloadPage() {
console.log('reload');
window.location.reload();
}
index.spec.ts:
import * as ref from './';
import sinon from 'sinon';
import { expect } from 'chai';
describe('57519517', () => {
let reloadStub;
before(() => {
reloadStub = sinon.stub(ref, 'reloadPage');
});
it('should mock reload page', () => {
const logSpy = sinon.spy(console, 'log');
ref.reloadPage();
expect(reloadStub.calledOnce).to.be.true;
expect(logSpy.notCalled).to.be.true;
});
});
Unit test result:
57519517
✓ should mock reload page
1 passing (12ms)
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/57519517

Related

Javscript Jest test framework: when is a mock unmocked?

I want to mock a module and a function for a specific test. I have the following:
test("my test", () => {
jest.mock("some_module")
some_module.some_function = jest.fn();
...
})
test("another test", () => {
...
});
My question is, when the test is finished, will both mocks be "unset" so that I can use the real implementation for my next test? Or do I have to explicitly remove all mocks myself?
when the test is finished will all of the mocks be "unset"?
Jest tests are sandboxed on a test-file basis, so ordinarily after all tests in that file have been run, all the mocks would be restored.
However, what you are doing there: some_module.some_function = jest.fn(); is not mocking via Jest's mocking mechanism, it is monkey-patching an imported function. This will not be removed by Jest.
You should be doing something like this instead:
import { some_function } from 'some-module-path';
jest.mock('some-module-path');
test('my test', () => {
...
expect(some_function).toHaveBeenCalled(); // e.g.
}
UPDATE:
After the discussion in comments, here is an example of doing a simple monkey-patch safely in Jest, so that it is restored for subsequent tests in the same file:
// foo.js -----------------------------------
export const foo = () => 'real foo';
// bar.js -----------------------------------
import { foo } from './foo';
export const bar = () => foo();
// bar.test.js ------------------------------
import { bar } from './bar';
import * as fooModule from './foo';
describe('with mocked foo', () => {
let originalFoo;
beforeAll(() => {
// patch it!
originalFoo = fooModule.foo;
fooModule.foo = () => 'mocked foo';
});
afterAll(() => {
// put it back again!
fooModule.foo = originalFoo;
});
test('mocked return value from foo()', () => {
expect(bar()).toEqual('mocked foo');
});
});
describe('with real foo', () => {
test('expect real return value from foo()', () => {
expect(bar()).toEqual('real foo');
});
});
UPDATE 2:
Another alternative, you can mock the dependency and use the original implementation temporarily with jest.requireActual:
import { bar } from './bar';
import { foo } from './foo';
jest.mock('./foo');
foo.mockReturnValue('mocked foo');
const fooModule = jest.requireActual('./foo')
test('mocked return value from foo()', () => {
expect(bar()).toEqual('mocked foo');
});
test('real return value from foo()', () => {
foo.mockImplementation(fooModule.foo);
expect(bar()).toEqual('real foo');
});

Jest: Vue Component can't find mocked function

I'm mocking a ES6 class which is used inside my Vue Component:
export default class DataUploadApi {
// Get uploaded files
static async getUploadedFiles() : Promise<Object> {
return WebapiBase.getAsync({uri: DATA_UPLOAD_ENPOINTS.FILES});
}
}
I've been following along with this document, but I think my syntax is slightly off with my mock:
import { mount } from '#vue/test-utils';
import DataUploadApi from '../webapi/DataUploadService';
import FileDownloadList from '../components/file-download-list.vue';
const mockGetUploadedFiles = jest.fn().mockResolvedValue({json: JSON.stringify(uploadedFilesObj)});
jest.mock('../webapi/DataUploadService', () => jest.fn().mockImplementation(() => ({getUploadedFiles: mockGetUploadedFiles})));
describe('file-download-list component', () => {
beforeEach(() => {
// #ts-ignore
DataUploadApi.mockClear(); // https://stackoverflow.com/a/52707663/1695437 dont use # imports on mocks.
mockGetUploadedFiles.mockClear();
});
describe('renders correct markup:', () => {
it('without any uploaded files', () => {
const wrapper = mount(FileDownloadList, {});
expect(wrapper).toMatchSnapshot();
});
});
});
This test passes. However, in the snapshot I can see that the API called failed with this error message:
<p>
_DataUploadService.default.getUploadedFiles is not a function
</p>
What have I done wrong with the function mock? Thanks in advance!
There seemed to be a few issues with my code:
Mocking the API
Using an internal mockImplementation seems to cause issues, and is not required if you don't need the additional mock functionality.
jest.mock('#/apps/gb-data/webapi/DataUploadService', () => ({
getUploadedFiles() {
return Promise.resolve({ uploaded_files: {} });
},
}));
Changes to the test
Both flushPromises and nextTick are required.
it('with uploaded files', async () => {
const wrapper = mount(FileDownloadList, {
stubs: fileDownloadListStubs,
});
await flushPromises();
await wrapper.vm.$nextTick();
expect(wrapper).toMatchSnapshot();
});

Sinon .callsFake() is not mocking the return of the function

I have the following code in my test file:
const stub1 = sinon.stub('../path/to/module', '_myFunc');
stub1.callsFake(function() {
console.log('223344');
});
Inside a beforeEach in Mocha, but when _myFunc gets called, it is not executing the console.log.
_myFunc is exported like this:
module.exports = {
_myFunc
}
What am i doing wrong?
Here is a working example:
index.ts:
function _myFunc() {
console.log('real implementation');
}
module.exports = {
_myFunc
};
index.spec.ts:
import { expect } from 'chai';
import sinon from 'sinon';
const mod = require('./index');
describe('mod', () => {
it('should stub function', () => {
const stub = sinon.stub(mod, '_myFunc').callsFake(() => {
console.log('223344');
});
mod._myFunc();
expect(stub.calledOnce).to.be.true;
});
});
Unit test result:
mod
223344
✓ should stub function
1 passing (8ms)
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/57044971

Jest: Change output of manual mock for different tests within a test suite

Let's say I have the following two files:
// index.js
...
import { IS_IOS } from 'common/constants/platform';
...
export const myFunction = () => (IS_IOS ? 'foo' : 'bar');
// index.test.js
...
import { myFunction } from './index';
jest.mock('common/constants/platform', () => ({ IS_IOS: true }));
describe('My test', () => {
it('tests behavior on IOS', () => {
expect(myFunction()).toBe('foo');
});
// --> Here I want to change the value of IS_IOS to false
it('tests behavior if NOT IOS', () => {
expect(myFunction()).toBe('bar');
});
});
As you see my mocking function returns IS_IOS: true. I want it to return IS_IOS: false after my first test. How would I do that?
I also tried an adaptation of the solution here but I couldn't get it work, because there the mock returns a function:
module.exports = {
foo: jest.genMockFunction();
}
whereas my mock should return a boolean value which is not called inside the file I'm testing.
That's what I did here:
// common/constants/__mock__/platform
export const setIsIos = jest.fn(val => (IS_IOS = val));
export let IS_IOS;
// index.test.js
...
import { IS_IOS, setIsIos } from 'common/constants/platform';
jest.mock('common/constants/platform');
describe('My test', () => {
setIsIos('foo');
it('tests behavior on IOS', () => {
expect(myFunction()).toBe('foo');
});
setIsIos('bar');
it('tests behavior if NOT IOS', () => {
expect(myFunction()).toBe('bar');
});
});
Oddly when console-logging, i.e. console.log(IS_IOS); I get the expected values. The test however seems to use the original value, i.e. undefined.
Add jest.resetModules() to the beforeEach() call of that describe() test suite:
describe('EventManager', () => {
beforeEach(() => {
jest.resetModules();
});
...
Additionally I found A more complete example on how to mock modules with jest here

Proxyquire not stubbing my required class

I have a class AProvider that requires './b.provider'.
const BProvider = require('./b.provider');
class AProvider {
static get defaultPath() {
return `defaults/a/${BProvider.getThing()}`;
}
}
module.exports = AProvider;
b.provider.js is adjacent to a.provider.js and looks like
global.stuff.whatever = require('../models').get('Whatever'); // I didn't write this!
class BProvider {
static getThing() {
return 'some-computed-thing';
}
}
module.exports = BProvider;
In my test I use proxyquire to mock out ./b.provider as follows:
import { expect } from 'chai';
import proxyquire from 'proxyquire';
describe('A Provider', () => {
const Provider = proxyquire('../src/a.provider', {
'./b.provider': {
getThing: () => 'b-thing'
},
});
describe('defaultPath', () => {
it('has the expected value', () => {
expect(Provider.defaultPath).to.equal('defaults/a/b-thing')
});
});
});
However when I run the test BProvider is still requiring the actual './b.provider' not the stub and BProvider's reference to global.stuff.whatever is throwing an error.
Why isn't this working?
The answer as to why this is happening is as follows
proxyquire still requires the underlying code before stubbing it out. It does this to enable callthroughs.
The solution is simply to explicitly disallow callthroughs.
The test becomes:
import { expect } from 'chai';
import proxyquire from 'proxyquire';
describe('A Provider', () => {
const Provider = proxyquire('../src/a.provider', {
'./b.provider': {
getThing: () => 'b-thing',
'#noCallThru': true
},
});
describe('defaultPath', () => {
it('has the expected value', () => {
expect(Provider.defaultPath).to.equal('defaults/a/b-thing')
});
});
});
Running this test works perfectly.

Categories

Resources