How can I can a mock an external class using jest? - javascript

I currently have the following Vue page code:
<template>
// Button that when clicked called submit method
</template>
<script>
import { moveTo } from '#/lib/utils';
export default {
components: {
},
data() {
},
methods: {
async submit() {
moveTo(this, COMPLETE_ACTION.path, null);
},
},
};
</script>
and then I have a test file for this page. My issue is that Im trying to check and assert that the moveTo method is called with the correct parameters using Jest. It keeps showing expected undefined but received an object. Here are the key points from the test file:
import * as dependency from '#/lib/utils';
dependency.moveTo = jest.fn();
// I then trigger the button call which calls the submit method on the page
expect(dependency.moveTo).toHaveBeenCalledWith(this, COMPLETE_ACTION.path, null);
Im unsure what this is in this context and what I should actually be passing in. Just to note I am using the mount helper from vue test utils.

I solved my issue and it was the this param within the test. This was undefined in the test and was expecting to match against a VueComponent.
I used my wrapper and then accessed the VueComponent by referencing the vm property as per the documentation: https://vue-test-utils.vuejs.org/api/wrapper/#properties
In turn I updated the following line and added wrapper.vm
expect(dependency.moveTo).toHaveBeenCalledWith(wrapper.vm, COMPLETE_ACTION.path, null);

You need to mock the module itself. In your case you are making an assertion on a spy function that is never called.
You can add a module mock by creating a "mocks/ subdirectory immediately adjacent to the module". For a node module "If the module you are mocking is a Node module (e.g.: lodash), the mock should be placed in the mocks directory adjacent to node_modules".
In your case (there are other approaches) you need to create a __mocks__ folder adjacent to the node_modules one and create a file at __mocks__/lib/utils/index.js and export the mocked function:
export const moveTo = jest.fn()

Related

Can't mock default export in tested file

I'm working on a Typescript project and I'm trying to add tests for functions that are sorting through MongoDB responses to get meaningful results. I've set up an in-memory mock of the database, now I just need to somehow get the test to use this mock database when running tests on the functions in my file.
I have a file called mongoClient.ts that is responsible for this MongoClient object that I need to mock:
import { MongoClient } from 'mongodb';
const client = new MongoClient(`${process.env.MONGO_CONNECT}`);
export default client;
The file that I need to test, called models.ts, imports mongoClient with an ES6 import statement:
import client from '../mongoClient';
In my test I have created a MongoMemoryServer (mongodb-memory-server), and I have made a class to handle the connection to this MongoMemoryServer:
class dbConnection {
server;
connection;
uri;
async init() {
this.server = await MongoMemoryServer.create();
this.uri = await this.server.getUri();
this.connection = await new MongoClient(this.uri);
return this.connection;
}
connect() {
this.connection.connect();
}
getClient() {
return this.connection;
}
getUri() {
return this.uri;
}
async stopIt() {
await this.server.stop();
}
}
I cannot seem to get the models object that I create in the test (with const models = require('../src/models');) to mock its import of 'mongoClient'.
I've tried the following:
let mock_db = new dbConnection();
jest.doMock('../src/mongoClient', () => ({
__es6Module: true,
default: mock_db.getClient()
}));
I tried to use mock(...) earlier; that throws this error when I run the test:
The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
In a beforeAll(...) I am able to fill this DB with dummy information so my functions in models will run correctly, but these functions can't see the mock in-memory database because I can't properly mock the mongoClient file that gets imported at the top of my models.ts. I don't want to have to change any code that isn't in a .test file to accomplish this, but I can't get this test environment to point at my mock in-memory database while running these tests. I must be missing something about the implementation of doMock, and I need some help to fix it.
- -EDIT_1- - I think this might have something to do with the order in which I'm declaring things. The class dbConnection is declared directly under my imports at the top of the test file. Then under that I instantiate the dbConnection class as mockDb and jest.doMock(... like in the example shown above. My tested function seems to be trying to use a mocked import that only contains the following:
{"__es6Module":true}
The error message I'm getting at this point is ERROR --- _get__(...).db is not a function.
The problem with this could be that it's mocking the imported module correctly, but the default export used to mock (mockDb.getClient()) is undefined when it's declared in that doMock statement near the top of the file. I don't know where else to move that doMock, and it breaks if I move it into the top-level beforeAll or into the describe section for these tests or into the test itself.
- -EDIT_2- - Changing __es6Module to __esModule (because the compiled js file looks for __esModule) makes it so that undefined is the contents of the imported class in my code.
The compiled js code has the following at the top:
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const mongoClient_1 = __importDefault(require("../util/mongoClient"));
- -FINAL_EDIT- - I've figured this out, it was an error on my part in how the order of the Jest tests were being executed. The default export client that I was mocking had not been initialized before the tests were being run. This required that I mock the default import in a beforeAll(...) statement inside of each 'describe' block of tests, and in each test I have to instantiate a new models at the start of the test with const models = require(...);. I have working tests now, and everything is good in life ;-)
I've figured this out, it was an error on my part in how the order of the Jest tests were being executed. The default export client that I was mocking had not been initialized before the tests were being run. This required that I mock the default import in a beforeAll(...) statement inside of each 'describe' block of tests, and in each test I have to instantiate a new models at the start of the test with const models = require(...);. I have working tests now

Vue.js 2 + Typescript - global variables becomes undefined after build

I have a Vue.js 2 project with Typescript. In the main.ts file, I've declared 2 variables, that I've wanted to access globally in my project:
// ...
Vue.prototype.$http = http; // this is the library imported from another file, contains various methods such as `get`, `post` etc.
Vue.prototype.$urls = urls; // this is JSON object, also imported from another file
new Vue({
store,
render: (h) => h(App),
}).$mount('#app');
In one of my components, let's call it User I have following mounted code block:
mounted(): void {
this.$http.get(`${this.$urls.getUser}/${this.userId}`);
}
Everything works fine when I'm running a local server (via npm run serve command), but when I create an app build (via npm run build command) and enter the app on the server (or the index.html file on my hdd) I receive following error:
TypeError: Cannot read property 'get' of undefined
at VueComponent.value (user.ts:62) // <-- this line is the one with $http.get from `mounted` hook
I'm not sure how to proceed with this, I've blindly tried to add those global values to various places e.g. in http.d.ts file I have the following:
import { KeyableInterface } from '#/interfaces/HelperInterfaces';
import Vue from 'vue';
declare module 'vue/types/vue' {
interface VueConstructor {
$http: KeyableInterface;
}
}
declare module 'vue/types/vue' {
interface Vue {
$http: KeyableInterface;
}
}
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
http?: KeyableInterface
}
}
(I've also created urls.d.ts with similar code)
UPDATE #1:
I've tried also following approach - in my main.ts file:
const helperModules = {
/* eslint-disable-next-line #typescript-eslint/no-explicit-any */
install: (vueInstance: any) => {
vueInstance.prototype.$http = http;
vueInstance.prototype.$urls = urls;
},
};
Vue.use(helperModules);
But it still doesn't work (same error).
UPDATE #2:
I've also imported http utility into my user component, and added following console.log to existing mounted callback:
console.log(http, this.$http)
And while working on my localhost, it returns me twice the same value, but when I create a build it returns me:
ModuleĀ {__esModule: true, Symbol(Symbol.toStringTag): "Module"}, undefined
Similar thing happens, when I add console.log(urls, this.$urls) - imported module is being logged, while prototyped one returns undefined.
Any thoughts? Will appreciate any help.
Finally I've overcome the problem by moving the prototyping parts from main.ts to App.ts file.
I'm not 100% sure if it's a valid "the Vue.js way" of solving this, as I've always declared that in main.js file - but I was using then JavaScript & it was "just working" as expected.

Mocking a module imported in a vue component with Jest

I'm having some issues processing the documentation of Jest, because I expected this code to work:
import Vue from 'vue';
import Router from '#/router/index';
import OrdersService from '#/services/orders.service';
jest.mock('#/services/orders.service');
describe('OrdersItem.vue', () => {
beforeEach(() => {
// mockClear does not exist
OrdersService.mockClear();
});
it('should render expected list contents', () => {
// Orders.mock is undefined
OrdersService.getAll.mockResolvedValue([ ... ]);
// ...
However it does not. It fails as-if OrdersService was never mocked. I've also tried stuff like:
jest.mock('#/services/orders.service', () => jest.fn());
jest.mock('#/services/orders.service', () => { getAll: jest.fn() });
First one replaces the whole service with a mock function (I'd like to achieve that automatic mocking functionality mentioned in the docs, where all the methods from the original are auto-replaced with mock fn).
Second one fails same way as the .mock call with just the module path.
What am I doing wrong here and why?
orders.service skeleton:
import axios from 'axios';
import config from '../config/config.json';
import Order from '../models/order';
class OrdersService {
constructor(httpClient) {
this.httpClient = httpClient;
}
getAll() {
// ...
}
}
export default new OrdersService(axios);
It looks like there is an issue in with jest.mock (#4262) concerning moduleNameMapper for module resolvers, aliases, path, whatever you want to call using #/something.
// you cannot use a module resolver (i.e. '#')
jest.mock('#/services/orders.service');
// you must use the full path to the file for the import and mock
import OrdersService from '../../src/services/orders.service';
jest.mock('../../src/services/orders.service');
Stay tuned to updates on the issue, looks like the last update was on 9/28.
Secondly, provided you fix the issue above, you are exporting a class instance not the class itself, as is done in the Jest example. Therefore, you will not have access to the clearMock method on the OrdersService but rather you can call clearMock on each mocked method on the class instance.
// mockClear will be undefined
OrdersService.mockClear();
// mockClear is defined
OrdersService.getAll.mockClear();
If you want to export the instance as you are, you could just clear all mocks using jest.clearAllMocks in the beforeEach or loop through all methods and call mockClear on each. Otherwise exporting the class itself will give you access to OrdersService.mockClear which will ...
Clear all instances and calls to constructor and all methods (ref)
This seems to be useful in cases where the mocked class is being used/instantiated in another class that you are trying to test, as in the jest example.
All of this has been tested and confirmed using Jest v23.6 and vue-cli v3.0.4.
Since the OrdersService is an instance of the class, it will return an object and you would need to mock all the properties exposed by this object manually.
You could try with the following implementation to mock your function. Reference docs
OrdersService.getAll = jest.fn(()=>{
// mock implementation here;
});
Hope this helps :)
You could try calling jest.resetModules() in the beforeEach block, that might cause the mocked service to be used
Try to import everything with an alias and set the mock on the alias.
import * as OrdersModule from '#/services/orders.service';
OrdersModule.getAll = jest.fn()
I found it in the bible:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules

Jest mocking default exports - require vs import

I have seen questions referring to the mocking of default exports with jest around here, but I don't think this has already been asked:
When mocking the default export of a dependency of a module that is being tested, the tests suite fails to run if the module imports the dependency with the ES6 import statement, stating TypeError: (0 , _dependency.default) is not a function It succeeds, however, if the module uses a require().default call instead.
In my understanding, import module from location directly translates to const module = require(location).default, so I am very confused why this is happening. I'd rather keep my code style consistent and not use the require call in the original module.
Is there a way to do it?
Test file with mock:
import './modules.js';
import dependency from './dependency';
jest.mock('./dependency', () => {
return {
default: jest.fn()
};
});
// This is what I would eventually like to call
it('calls the mocked function', () => {
expect(dependency).toHaveBeenCalled();
});
Dependency.js
export default () => console.log('do something');
module.js (not working)
import dependency from './dependency.js';
dependency();
module.js (working)
const dependency = require('./dependency.js').default;
dependency();
You can use either es6 import or require js to import your js files in your jest tests.
When using es6 import you should know that jest is trying to resolve all the dependencies and also calls the constructor for the class that you are importing. During this step, you cannot mock it. The dependency has to be successfully resolved, and then you can proceed with mocks.
I should also add that as can be seen here jest by default hoists any jest.mocks to the top of the file so the order in which you place your imports does not really matter.
Your problem though is different. Your mock function assumes that you have included your js file using require js.
jest.mock('./dependecy', () => {
return {
default: jest.fn()
};
});
When you import a file using require js, this is the structure it has:
So assuming I have imported my class called "Test" using require js, and it has method called "doSomething" I could call it in my test by doing something like:
const test = require('../Test');
test.default.doSomething();
When importing it using es6 import, you should do it differently though. Using the same example:
import Test from '../Test';
Test.doSomething();
EDIT: If you want to use es6 import change your mock function to:
jest.mock('./dependecy', () => jest.fn());
the short answer for ES module if you want to use
import dependency from 'dependency'
jest.mock('dependency', () => ({
...jest.requireActual('dependency'),
__esModule: true,
default: jest.fn(),
}))
Have you tried something like this? I was dealing with the default export mocking for months until I found this.
jest.mock('./dependency', () => () => jest.fn());
The idea behind this line is that you are exporting a module that is a function. So you need to let Jest knows that it has to mock all your ./dependency file as a function, that returns a jest.fn()

How do you manually mock one of your own files in Jest?

I'm trying to mock an object (which I created) in Jest so I can provide default behaviour within the react component (so the real implementation isn't used)
This is my react component ChatApp (it's very straight forward)
'use strict';
var React, ChatApp, ChatPanel, i18n;
React = require('react');
ChatPanel = require('./chat_panel');
i18n = require('../support/i18n');
ChatApp = React.createClass({
render() {
return (
<div className="chat-app">
<h1>{i18n.t("app.title")}</h1>
<ChatPanel />
</div>
);
}
});
module.exports = ChatApp;
So I have a custom I18n dependency that does translations (I18n is something I've written that is a wrapper for node-polyglot).
So I want to do a basic test to see if the H1 has the correct word in it, but I don't want to set jest.dontMock() on my I18n object, because I don't want it to use the real object in the ChatApp test.
So following the basic instructions on the jest website, I created a mocks folder and created a mock file for i18n, which generates a mock from the original object and then overrides the t method and adds a method to allow me to set the return string for t.
This is the mock object
'use strict';
var i18nMock, _returnString;
i18nMock = jest.genMockFromModule('../scripts/support/i18n');
_returnString = "";
function __setReturnString(string) {
_returnString = string;
}
function t(key, options = null) {
return _returnString;
}
i18nMock.t.mockImplementation(t);
i18nMock.__setReturnString = __setReturnString;
module.exports = i18nMock;
Now in my ChatApp test I require the mock in a before each, like so:
'use strict';
var React, ChatApp, TestUtils, path;
path = '../../../scripts/components/';
jest.dontMock( path + 'chat_app');
React = require('react/addons');
ChatApp = require( path + 'chat_app');
TestUtils = React.addons.TestUtils;
describe('ChatApp', () => {
beforeEach(() => {
require('i18n').__setReturnString('Chat App');
});
var ChatAppElement = TestUtils.renderIntoDocument(<ChatApp />);
it('renders a title on the page', () => {
var title = TestUtils.findRenderedDOMComponentWithTag(ChatAppElement, 'h1');
expect(title.tagName).toEqual('H1');
expect(title.props.children).toEqual('Chat App');
});
});
If i console.log the i18n object within the test then I get the correct mocked object, the __setReturnString also gets triggered (as if I console.log in that message I see the log).
However, if I console.log the i18n object within the actual React component then it gets a Jest mock but it doesn't get my Jest mock, so the t method is an empty method that doesn't do anything, meaning the test fails.
Any ideas what I'm doing wrong?
Thanks a lot
I've had trouble getting the __mocks__ folder working as well. The way I got around it is by using the jest.setMock(); method.
In your case, you would jest.setMock('../../../scripts/i18n/', require('../__mocks__/i18n');
Obviously, I am not certain of the location of your mock and the location of the real library you're using, but the first parameter should use the path where your real module is stored and the second should use the path where your mock is stored.
This should force your module and all modules that yours require (including React) to use your manually mocked i18n module.
Jest does automatic mocking. Just i18n = require('../support/i18n') should be enough. That's why you usually have to call jest.dontMock in the first place.
You can find more information here: https://facebook.github.io/jest/docs/automatic-mocking.html
What mattykuzyk mentions in his answer did not work at all for me :(
However, what I found out seemed to be the problem for me was the setup of jest: I used moduleNameMapper in the beginning, and for some reason these are never mocked...
So for me the first step was to instead move my module name mapped folder to the moduleDirectories to get anything to work.
After that, I could simply add a __mocks__ file adjacent to the actual implementation (in my case utils/translation.js and utils/__mocks__/translation.js).
As my translations.js default exports a translation function, I also default exported my mock. The entire __mocks__/translations.js is super simply and looks like this:
export default jest.fn((key, unwrap = false) => (
unwrap && `${key}-unwrapped` || `${key}-wrapped`
))
Although I haven't tested it, adding a __setReturnString should be easy enough, for me it was sufficient to actually return my translation key. Hope this helps!

Categories

Resources