Can't mock default export in tested file - javascript

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

Related

How to properly export/import an object in this nodeJS project?

I have a nodeJS project where I have this file GameState.js with this structure:
const GameState = function(socket){
this.socket = socket
//quite a large constructor so I removed most of it
};
//defining all the prototype methods
GameState.prototype.getSocket = function() {
return this.socket
}
module.exports = GameState;
I'm able use this GameScreen constructor in my gamescreen.js file, by having the two scripts in my gamescreen.html file like this:
<script src = "gamestate.js"></script>
<script src = "gamescreen.js"></script>
So for getting GameState into gamescreen.js I actually don't need the module.exports, it's even giving me a (non-breaking) ReferenceError when I use the app, which is quite annoying.
However with my current structure I can't remove this module.exports as I also have a test file (using jest) where I import GameState with require like this:
const GameState = require("../scripts/gamestate.js");
//the tests here...
So my question is: How do I get GameState in both gamescreen.js and gamestate.test.js, without having the ReferenceError? Right now it's all working, but it's not very optimal to get an error in the console when running the app.
EDIT: A better way to formulate this question might be: I have a module GameState defined with module.exports, now how do I get it in gamescreen.js (the client-side), without losing the ability to import it with require(...) in a test file?
I got rid of the error by simply wrapping the module.exports in a try catch block like this:
try {
module.exports = GameState;
} catch(error) {
console.log("didn't work")
}
When I run it in the browser it functions normally and prints: "didn't work" and when I run my tests with npm test, the tests work too.
It might not be the prettiest fix, so any other suggestions would be appreciated.

Spying On/Mocking Import of an Import

I'm writing unit tests using vitest on a VueJS application.
As part of our application, we have a collection of API wrapper services, e.g. users.js which wraps our relevant API calls to retrieve user information:
import client from './client'
const getUsers = () => {
return client.get(...)
}
export default {
getUsers
}
Each of these services utilise a common client.js which in turn uses axios to do the REST calls & interceptor management.
For our units tests, I want to check that the relevant url is called, so want to spy on, or mock, client.
I have followed various examples and posts, but struggling to work out how I mock an import (client) of an import (users.js).
The closest I've been able to get (based on these posts - 1, 2) is:
import { expect, vi } from 'vitest'
import * as client from '<path/to/client.js>'
import UsersAPI from '<path/to/users.js>'
describe('Users API', () => {
beforeEach(() => {
const spy = vi.spyOn(client, 'default') // mock a named export
expect(spy).toHaveBeenCalled() // client is called at the top of users.js
})
test('Users API.getUsers', () => {
UsersAPI.getUsers()
expect(spy).toHaveBeenCalled()
})
})
but it's tripping on:
❯ async frontend/src/api/client.js:3:31
2| import store from '#/store'
3|
4| const client = axios.create({
| ^
5| headers: {
6| 'Content-Type': 'application/json'
where it's still trying to load the real client.js file.
I can't seem to mock client explicitly because the import statements run first, and so client is imported inside users.js before I can modify/intercept it. My attempt at the mocking was as follows (placed between the imports and the describe):
vi.mock('client', () => {
return {
default: {
get: vi.fn()
}
}
})
Mocking a module
vi.mock()'s path argument needs to resolve to the same file that the module under test is using. If users.js imports <root>/src/client.js, vi.mock()'s path argument needs to match:
// users.js
import client from './client' // => resolves to path/to/client.js
// users.spec.js
vi.mock('../../client.js') // => resolves to path/to/client.js
It often helps to use path aliases here.
Spying/mocking a function
To spy on or mock a function of the mocked module, do the following in test():
Dynamically import the module, which gets the mocked module.
Mock the function off of the mocked module reference, optionally returning a mock value. Since client.get() returns axios.get(), which returns a Promise, it makes sense to use mockResolvedValue() to mock the returned data.
// users.spec.js
import { describe, test, expect, vi } from 'vitest'
import UsersAPI from '#/users.js'
vi.mock('#/client')
describe('Users API', () => {
test('Users API.getUsers', async () => {
1️⃣
const client = await import('#/client')
2️⃣
const response = { data: [{ id: 1, name: 'john doe' }] }
client.default.get = vi.fn().mockResolvedValue(response)
const users = await UsersAPI.getUsers()
expect(client.default.get).toHaveBeenCalled()
expect(users).toEqual(response)
})
})
demo
Late to the party but just in case anyone else is facing the same issue.
I solved it by importing the module dependency in the test file and mocking the whole module first, then just the methods I needed.
import { client } from 'client';
vi.mock('client', () => {
const client = vi.fn();
client.get = vi.fn();
return { client }
});
Then in those tests calling client.get() behind the scenes as a dependency, just add
client.get.mockResolvedValue({fakeResponse: []});
and the mocked function will be called instead of the real implementation.
If you are using a default export, look at the vitest docs since you need to provide a default key.
If mocking a module with a default export, you'll need to provide a default key within the returned factory function object. This is an ES modules specific caveat, therefore jest documentation may differ as jest uses commonJS modules.
I've accepted the above answer, as that did address my initial question, but also wanted to include this additional step I required.
In my use case, I need to mock an entire module import, as I had a cascading set of imports on API files that in turn, imported more and more dependencies themselves.
To cut this, I found this in the vuex documentation about mocking actions:
https://vuex.vuejs.org/guide/testing.html#testing-actions
which details the use of webpack and inject-loader to substitute an entire module with a mock, preventing the source file loading at all.

where do module.exports export your function and what is the use of it when we still use require to import the code into your module

i Have written the following code in node.js
notes.js
console.log('notes app is running');
app.js
const notes = require('./notes.js');
console.log(notes);
When i import the code and run app.js output is shown as notes app is running
Now i updated the code for notes.js
console.log('notes app is running');
addNote = () =>{
console.log('addNote');
return 'New note';
} ;
Now i want to use the following arrow function in my code so updtaed my
app.js
const notes = require('./notes.js');
var res = notes.addNote();
console.log(res);
console.log(notes);
Now it is Throwing me error
notes.addNote is not a function
1) I know i should use module.exports.addNote
2) But i want to know why we can see a log which we have written in notes.js without using module.exports statment. why can't we use require statment and store total code and call the function from that varable as we do for a instance of a class
3)More preciously where do module.export export your code (i mean to which directrey )
4)Plese correct me if anything is wrong
(#1 and #4 don't need answers, so I've left them off.)
2) But i want to know why we can see a log which we have written in notes.js without using module.exports statment.
With Node.js's style of modules (which is a flavor of CommonJS), a module is loaded and executed when it's first required. Your console.log is in the module's code, so when you require it (the first time), that code gets run.
why can't we use require statment and store total code and call the function from that varable as we do for a instance of a class
You can, if that's what you want to do:
exports = {
// code here as object properties
addNote: () => {
console.log('addNote');
return 'New note';
}
};
and
const mod = require("./notes.js");
mode.addNote();
3)More preciously where do module.export export your code (i mean to which directrey )
To the module cache in memory.
Internally, node caches all modules. In order to do this, it starts at the entry point file (e.g. you app.js file) and recursively searches all require statements (or imports).
As node parses modules, any code at the top level of the file will execute - such as your console.log line
console.log('notes app is running');
However, note, that at this point nothing in the file has been exposed to any other part of your codebase. Instead, node takes any value that is exported via module.exports and adds it to an internal cache. This cache is keyed on the path to the file as appeared in the require statements (converted to an absolute path), so for example, the following require statements:
const module1 = require('../module1.js');
const module2 = require('../module2.js');
will result in cache entries which look like:
<path_to>/../module1.js = ... contents of module1.exports
<path_to>/../module2.js = ... contents of module2.exports
Any time you require one of those modules again, you will get the cached version of the modules, it will NOT re-parse the file. For your example, it means that not matter how many times you require the notes.js file, it will only print your console.log('notes app is running'); statement once.
Because of the way node loads modules in isolation, you can ONLY access the elements which are exported via module.exports. Which means any function you define in the file but do not export cannot be accessed.
So, to directly address your questions:
I know i should use module.exports.addNote
Yes. Though, not you can also assign a new object to module.exports, e.g module.exports = { addNote };
But i want to know why we can see a log which we have written in notes.js without using module.exports statment. why can't we use require statment and store total code and call the function from that varable as we do for a instance of a class
Because node parses all required files while generating it's cache
More preciously where do module.export export your code (i mean to which directrey )
They're not stored in a directory, but rather cached in memory based on the file name and contents of module.exports
Plese correct me if anything is wrong
guess this one doesn't need an answer

How to stub constant functions when using ES Modules with sinon.js?

With ES Modules, we define some exported functions as const and it is often a standard practice. However, during unit testing, these const functions can't be stubbed out which seems problematic from a unit testing perspective. For example,
import * as fs from 'fs';
import { bindNodeCallback } from 'rxjs';
import { map } from 'rxjs/operators';
// readFile :: string, string => Observable<string>
export const readFile$ = bindNodeCallback(fs.readFile);
// readJson :: string => Observable<any>
export function readJson(fileName) {
return readFile$(fileName, 'utf-8')
.pipe(map((rawFile) => JSON.parse(rawFile)));
}
Now to unit test readJson, I would typically want to stub readFile$ function. Unfortunately, following Sinon code doesn't work:
// Setup data - stubs / mocks
const stub = sinon.stub(myFs, 'readFile$').returns(json$);
Since Sinon is simply changing reference myFs.readFile$, original const still points to the original function which is in turn called by readJson function.
Any suggestion - how can I really stub/mock constant function within the same module?
const is constant one can't change it using "normal" code. Unfortunately sinon is not magic. You need to instrument your code to allow changing constant value.
Assuming you are using babel to transpile you could use babel-plugin-rewire.
After adding it to your babel config you would be able to do the following injection in your test code
import { default as readJson, __RewireAPI__ as rewire } from './path/to/readJson.js'
const stub = sinon.stub(myFs, 'readFile$').returns(json$)
rewire.__set__('readFile$', stub)
// readJson() would now call provided stub
I believe Sinon has a way to do this, without the use of third party packages (e.g. Babel). Here is their documentation page about it, with a simple example: How to stub a dependency of a module. I just tried it myself and it seems to work.
Sorry, I don't have time to share any code here in my reply. I know that is poor form :-/ But hopefully the link helps some people.

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