Proxyquire shows error "Cannot find module" - javascript

I'm trying to use proxyquire to replace a private function for testing in my Meteor app.
Meteor 1.6.1
meteortesting:mocha#1.1.2
In my parentFunction.js:
import { some function } from 'anotherFile';
function childFunction() {
...
return someValue;
}
export default function parentFunction() {
return childFunction()
}
In my test file:
const proxyquire = require('proxyquire');
if (Meteor.isServer) {
...
describe('parentFunction', () => {
it('uses the mocked child function', () => {
const testThing = proxyquire('./parentFunction', {
'childFunction': () => ({ 'name': 'bob' }),
});
});
}
parentFunction.js is in the same folder as my test file, and just to double check the path, I made sure this works:
import parentFunction from './parentFunction';
But when I run the test, I'm seeing an error:
Error: Cannot find module './parentFunction.js'
What am I doing wrong? I've tried an absolute path, that didn't work. And as far as I can see from the documentation a relative path in the file where proxiquire is required, should be fine.
Thanks for any help!

Related

Can I put my jest mock in a separate file? manual mock

In my get-sites.spec.js I created a mock like this which works perfectly fine:
jest.mock('#aws-sdk/client-secrets-manager', () => {
const SecretsManagerClient = jest.fn().mockImplementation(() => {
const result = {
SecretString: "{\"server\":\"host\",\"database\":\"database\",\"user\":\"userName\",\"password\":\"password\"}"
};
return {
send: jest.fn().mockResolvedValue(result)
};
});
const GetSecretValueCommand = jest.fn().mockImplementation(() => {
return {}
});
return {
SecretsManagerClient,
GetSecretValueCommand
}
});
I've tried creating the following directory structure and moved the mock code to the client-secrets-manager.js file.
__tests__/
|- get-sites.spec.js
__mocks__/
|- #aws-sdk/
|-- client-secrets-manager.js
src/
|- get-sites.js
Then in my get-sites.spec.js file I changed the mock code to jest.mock('#aws-sdk/client-secrets-manager');
When I run the test I get an error: TypeError: Cannot read properties of undefined (reading 'SecretString'). Is there some way to move my mock to a separate file to make it available to all my unit tests that will preserve the functionality?
Turns out I was close. I changed the client-secrets-manager.js file in the mocks folder to look like this:
const SecretsManagerClient = jest.fn().mockImplementation(() => {
return {
send: jest.fn()
};
});
const GetSecretValueCommand = jest.fn().mockImplementation(() => {
return {}
});
module.exports = {
SecretsManagerClient,
GetSecretValueCommand
}
Then in my get-sites.spec.js file I added this line to the test:
const sendResponse = {
SecretString: "{\"server\":\"host\",\"database\":\"database\",\"user\":\"userName\",\"password\":\"password\"}"
};
jest.spyOn(SecretsManagerClient.prototype, 'send').mockResolvedValue(sendResponse);
Then I was able to assert it was called like this:
expect(SecretsManagerClient).toHaveBeenCalledTimes(1);
expect(SecretsManagerClient.prototype.send.mock.calls.length).toEqual(1);

Mock uuid in jest - uuid called in service function

I have a jest test that is calling the real function and it compares the result returned with an expected result. The service function called uses uuid. I have all kind of errors while trying to mock uuid and can't seem to succeed.
My code is:
import uuid from 'uuid';
import tinyRuleset from './tiny_ruleset.json';
import { Store } from '../store';
describe('TuningStore test ', () => {
let store;
let db;
beforeEach(async () => {
db = levelup(encode(memdown(), { valueEncoding: 'json' }));
store= new Store(db);
});
test('createObject()', async () => {
jest.spyOn(uuid, 'v4').mockReturnValue('abc22');
const obj = await store.createObject();
expect(obj ).toEqual({
a: expect.any(string),
b: 'tiny_ruleset',
v: expect.any(Function)
});
});
})
I tried several ways, but none of them worked. My current error is: uuid is not a function. Also tried this:
const uuidv4Spy = jest.spyOn(store.$uuid, 'v4').mockReturnValueOnce('fake uuid');
Basically uuid is used inside the store.createObject() function.
Thank you!
As explained here Mock uuid
const uuidMock = jest.fn().mockImplementation(() => {
return 'my-none-unique-uuid';
});
jest.mock('uuid', () => {
return uuidMock;
});
you need to apply the mock in the test file before you are importing your real file.

Jest URL.createObjectURL is not a function

I'm developping a reactJs application. I'm using jest to test my application.
I want to test a function that download a blob.
But unfortunately I receve this error:
URL.createObjectURL is not a function
my test function:
describe('download', () => {
const documentIntial = { content: 'aaa' };
it('msSaveOrOpenBlob should not have been called when navigao is undefined', () => {
window.navigator.msSaveOrOpenBlob = null;
download(documentIntial);
expect(window.navigator.msSaveOrOpenBlob).toHaveBeenCalledTimes(0);
});
});
The function I want to test:
export const download = document => {
const blob = new Blob([base64ToArrayBuffer(document.content)], {
type: 'application/pdf',
});
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob);
return;
}
const fileURL = URL.createObjectURL(blob);
window.open(fileURL);
};
This would appear to be as simple as setting up URL on the Global in Jest. Something like
describe('download', () => {
const documentIntial = { content: 'aaa' };
global.URL.createObjectURL = jest.fn();
it('msSaveOrOpenBlob should not have been called when navigao is undefined', () => {
global.URL.createObjectURL = jest.fn(() => 'details');
window.navigator.msSaveOrOpenBlob = jest.fn(() => 'details');
download(documentIntial);
expect(window.navigator.msSaveOrOpenBlob).toHaveBeenCalledTimes(1);
});
});
This should result in a test that you can also use for checking if global.URL.createObjectURL was called. As a side note: you may also run into a similar issue with window.open I would suggest mocking that as well if this becomes the case.
Since window.URL.createObjectURL is not (yet) available in jest-dom, you need to provide a mock implementation for it.
Don't forget to reset the mock implementation after each test.
describe("your test suite", () => {
window.URL.createObjectURL = jest.fn();
afterEach(() => {
window.URL.createObjectURL.mockReset();
});
it("your test case", () => {
expect(true).toBeTruthy();
});
});
jsdom, the JavaScript implementation of the WHATWG DOM used by jest doesn't implement this method yet.
You can find an open ticket about this exact issue on their github page where some workarounds are provided in comments. But if you need the blobURL to actually work you'll have to wait this FR is solved.
Workaround proposed in the comments of the issue for jest:
function noOp () { }
if (typeof window.URL.createObjectURL === 'undefined') {
Object.defineProperty(window.URL, 'createObjectURL', { value: noOp})
}
You just have to Write this in your setupTest.js
window.URL.createObjectURL = function() {};
The package jsdom-worker happens to provide this method, as well as adding support for web workers. The following worked for me:
npm install -D jsdom-worker
Then in package.json, edit or add a jest key:
{
...
"jest": {
"setupFiles": [
"jsdom-worker"
]
}
}
Just mocking the function global.URL.createObjectURL did not work for me, because the function was used by some modules during import and I got the error Jest URL.createObjectURL is not a function during import.
Instead it did help to create a file mockJsdom.js
Object.defineProperty(URL, 'createObjectURL', {
writable: true,
value: jest.fn()
})
Then import this file as the first import in your file containing the test
import './mockJsdom'
import { MyObjects} from '../../src/lib/mylib'
test('my test', () => {
// test code
}
Found here: https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom

Require not behaving as expected

I'm using the proxyquire library, which mocks packages on import.
I'm creating my own proxyquire function, which stubs a variety of packages I use regularly and want to stub regularly (meteor packages, which have a special import syntax):
// myProxyquire.js
import proxyquire from 'proxyquire';
const importsToStub = {
'meteor/meteor': { Meteor: { defer: () => {} } },
};
const myProxyquire = filePath => proxyquire(filePath, importsToStub);
export default myProxyquire;
Now I want to write a test of a file which uses one of these packages:
// src/foo.js
import { Meteor } from 'meteor/meteor'; // This import should be stubbed
export const foo = () => {
Meteor.defer(() => console.log('hi')); // This call should be stubbed
return 'bar';
};
And finally I test it like this:
// src/foo.test.js
import myProxyquire from '../myProxyquire';
// This should be looking in the `src` folder
const { foo } = myProxyquire('./foo'); // error: ENOENT: no such file
describe('foo', () => {
it("should return 'bar'", () => {
expect(foo()).to.equal('bar');
});
});
Note that my last 2 files are nested inside a subfolder src. So when I try to run this test, I get an error saying that the module ./foo couldn't be found, as it is being looked for in the "root" directory, where the myProxyquire.js file is, not the src directory as expected.
You might be able to work around that (expected) behaviour by using a module like caller-path to determine from which file myProxyquire was called, and resolving the passed path relative to that file:
'use strict'; // this line is important and should not be removed
const callerPath = require('caller-path');
const { dirname, resolve } = require('path');
module.exports.default = path => require(resolve(dirname(callerPath()), path));
However, I have no idea of this works with import (and, presumably, transpilers).

Test module.hot with Jest

I'm trying to get coverage to 100% for a module with hot module reloading setup.
In my module I have this:
// app.js
if (module && module.hot) module.hot.accept();
In the test file I am trying to do this
// app.test.js
it('should only call module.hot.accept() if hot is defined', () => {
const accept = jest.fn();
global.module = { hot: { accept } };
jest.resetModules();
require('./app');
expect(accept).toHaveBeenCalled();
}
);
But when I log out module in app.js it shows the require stuff but doesn't contain the hot method set by test.
If you have a variable referencing the module object, then you can inject a mock module object into that variable for the test. For instance, you could do the following:
// app.js
// ...
moduleHotAccept(module);
// ...
export function moduleHotAccept(mod) {
if (mod && mod.hot) {
mod.hot.accept();
}
}
Which can be tested like so:
// app.test.js
import { moduleHotAccept } from './app'
it('should only call hot.accept() if hot is defined', () => {
const accept = jest.fn();
const mockModule = { hot: { accept } };
moduleHotAccept(mockModule);
expect(accept).toHaveBeenCalled();
}
);
it('should not throw if module is undefined', () => {
expect(moduleHotAccept).not.toThrow();
}
);
it('should not throw if module.hot is undefined', () => {
expect(
() => moduleHotAccept({notHot: -273})
).not.toThrow();
}
);
I've needed it as well without the ability to pass it from outside.
My solution was to use a jest "transform" that allows me to modify a bit the code of the file that is using module.hot.
So in order to setup it you need to add:
// package.json
"transform": {
"file-to-transform.js": "<rootDir>/preprocessor.js"
//-------^ can be .* to catch all
//------------------------------------^ this is a path to the transformer
},
Inside preprocessor.js,
// preprocessor.js
module.exports = {
process(src, path) {
if( path.includes(... the path of the file that uses module.hot)) {
return src.replace('module.hot', 'global.module.hot');
}
return src;
},
};
That transformer will replace module.hot to global.module.hot, that means that you can control it value in the tests like so:
// some-test.spec.js
global.module = {
hot: {
accept: jest.fn,
},
};
Hope that it helps.

Categories

Resources