Mock not being called in Jest - unit-testing

Please help me understand Jest mocks.
I've put some dummy functions in a file:
// actions.js
export function f1() {
return 1
}
export function calls_f1() {
f1()
}
And then in my test file I'm trying to understand how to check that a function calls another function:
import * as actions from './actions.js'
describe("MOCKS", () => {
actions.f1 = jest.fn();
actions.calls_f1();
expect(actions.f1).toBeCalled();
});
But the test is failing saying the mock function wasn't called. I've also tried swapping the 2nd and 3rd lines of the test, to no avail.
My jest config is all good, I've actually been doing a bunch of other testing (in this same file) that works.
What am I missing here?
Note: The actual implementation of this (that I'm simplifying greatly here) involves an actions file that includes a public export function fetchStations() that calls a private (or, rather, not exported) export function _downloadStations(). I'm trying to test that _downloadStations() is called.
I'm using import * as actions only for convenience, so I can write that line and then use whatever functions that file exports under actions.whatever() (instead of having to add functions to the import statement when I decide to use them). If import * as actions has some effect I'm not noticing (as implied by brian below) then I certainly don't have to use it and can use import {thisAction, thatAction} from './actions' of course.

This line:
import * as actions from './actions.js'
binds the module exports from actions.js to actions...
...so setting actions.f1 to a mock function replaces the module export for f1...
...but this doesn't affect calls_f1 since it calls f1 directly.
If calls_f1 is changed to call the module export for f1 then it will call the mock function.
There are two ways to make that happen.
One way is to move f1 into its own module.
The other way is to note that ES6 modules "support cyclic dependencies automatically" (a major design goal of ES6 modules) so a module can import its own exports:
actions.js
import * as actions from './actions'; // <= import the module...
export function f1() {
return 1
}
export function calls_f1() {
actions.f1() // <= ...and use it to call f1
}
actions.test.js
import * as actions from './actions.js'
describe('actions', () => {
it('calls_f1 should call f1', () => {
actions.f1 = jest.fn();
actions.calls_f1();
expect(actions.f1).toBeCalled(); // Success!
})
})
Update
OP updated the question to indicate that the function in question is not exported from the module.
In that case it is just an internal implementation detail of the module and cannot be spied on directly.
Testing it would involve testing for the effects that it causes, and not directly spying on it to see if it was called.

Related

Ensuring a sandboxed mock refers to the same module instance as the Jest environment already has loaded

This is a bit hairy, so bare with me for a second :) Our Jest environment, custom-env, requires a module A and sets adds a function to the global scope using this.global.getBar = A.getBar.
The custom environment is used by a test. This test has a dependency to another module B, which is mocked out using jest.mock('../../moduleB', () => require('./FakeModuleB')). FakeModuleB is using the methods on module A.
Module A looks like this:
const bar = generateUuid();
module.exports = { getBar(){ return bar; } }
The weird thing here is that what is returned by getBar() differs depending on whether or not it is loaded through jest.mock() or loaded by the Jest environment. Essentially, jest.mock() seems to have cleared the loading cache somehow, so it loads the module afresh, which makes me end up with two instances of the module.
I am wondering what the "right" way of handling this could be?
The hackish way is of course to realize what this is and expose a reference to the loaded module on the global scope and refer to that in the require calls in ./FakeModuleB (which is loaded by jest.mock()):
// import { getBar } from '../../A'
const { getBar } = global.aModule;
I have then also exposed them in the jest env setup() method:
const { getBar } = aModule
this.global.aModule = aModule
this.global.getBar = getBar
Of course, I do not like this approach, polluting the global namespace for the sake of hacking around loader issues, but I am not sure how to approach this in another way.

Mocking Observable with Jest - rxjs

I have a simple Observable piping from another Observable that I want to test.
const loginState$ = messageBusObservables.loginState$.pipe(
startWith({ isLoggedIn: false })
pluck('isLoggedIn'),
distinctUntilChanged()
)
messageBusObservables is an object of observables. Where loginState$ is an Observable.
In my tests, I thought I would easily be able to mock the './messageBus' module like this: (how the module is imported is irrelevant, but import preferred)
import { of } from 'rxjs'
import './messageBus'
jest.mock('./messageBus', () => ({
loginState$: of({ isLoggedIn: true }),
}))
However, Jest throws the error:
babel-plugin-jest-hoist: The module factory of jest.mock() is not allowed to reference any out-of-scope variables.
Invalid variable access: of
I have tried, putting it in a jest.fn() I have tried extracting of({ isLoggedIn: true }) to a variable. But I keep getting the same error from jest.
So how can I mock the input into my Observables using Jest? I'll run into the same problem with other observables using .merge, .zip etc.
It needs to be a real observable that is the input of my other observables. I just want to mock the value with something like of() rather than mocking an object, with a method on it, that returns an object with a .pipe method etc. (I don't want to mock the functionality of an Observable). I want to pass it a real observable with a value set in my unit test.
I also need these mocks to be dynamic. So the mock from 1 assertion can be different from the mock in the next assertion. (clearing them with something like a beforeEach)
EDIT:
I also tried to use babel-plugin-rewire to mock this module, this worked fine in the *.test.js file where I was mocking it. But in the actual file no matter what I set the export to using rewire, it always got imported as the original Observable.
the reason you are getting this message:
babel-plugin-jest-hoist: The module factory of jest.mock() is not allowed to reference any out-of-scope variables. Invalid variable access: of
is because jest automatically hoists calls to jest.mock so that they happen before the imports.
You have two options to get around this default behaviour, the simple way is to use jest.doMock which is NOT hoisted:
jest.doMock('./messageBus', () => ({
loginState$: of({ isLoggedIn: true }),
}))
Alternatively, you can prefix all the variables referenced inside the mock factory passed to jest.mock with "mock":
const mockMessageBus = {
loginState$: of({ isLoggedIn: true }),
}
jest.doMock('./messageBus', () => mockMessageBus)
(note that you are responsible for ensuring all mock variables referenced in the factory function are in scope when jest.mock is called)
You're close.
You are trying to mock the module by passing a module factory as the second parameter to jest.mock. The main constraint of that approach is that the module factory must be completely self-contained and "is not allowed to reference any out-of-scope variables".
Referencing of from rxjs in the module factory (as you have found) breaks that constraint and causes the error you are seeing.
Fortunately there are other ways to mock modules.
From what I can see of your code it looks like the easiest approach would be to create a Manual Mock of the messageBus module.
Create a __mocks__ folder in the same directory as messageBus.js and create the mock (also called messageBus.js) within the __mocks__ folder.
__mocks__/messageBus.js will look something like this:
import { of } from 'rxjs'
export default {
loginState$: of({ isLoggedIn: true })
}
Then tell Jest you want to use the manual mock within your test by calling
jest.mock('messageBus');
at the top of your test file.
That call is hoisted by Jest and ensures that any code that imports messageBus during the test will get the mocked module.

Using a type as a value in typescript

I'm using inject-loader to mock dependencies for a Typescript project being unit tested. The service I'm testing has an import line like this:
import pnp, { SearchQuery, Sort, SortDirection, CamlQuery } from "sp-pnp-js";
For my test, I want to mock several of the functions on pnp, but keep the classes intact. In my unit test file, I've included this import:
import { SearchQuery, Sort, SortDirection, CamlQuery } from "sp-pnp-js";
Which gives my test access to the necessary classes. I've declared a mock service object:
// mock services
const mockPnp = {
default: { ... }
};
Which I'm wiring into my target class instance:
Service = require("inject!./sharepointservice")({
"sp-pnp-js": mockPnp
}).Service;
And everything works, so long as I don't run any code that references those classes (ie. SearchQuery). To get that to work, I tried adding it to the mock:
// mock services
const mockPnp = {
default: { ... },
SearchQuery: SearchQuery
};
However, I get a 'SearchQuery' only refers to a type, but is being used as a value here error.
I've tried casting to any (same error), tricks with modules and exports, with no luck. I'm supposed to be able to write any Javascript with Typescript, and this would work trivially in Javascript - what am I missing here?
According to the definition file SearchQuery is an interface, which means that you can not treat it as a value (as the error message says).
Typescript interfaces aren't being compiled into the js output, and you can not use them at runtime.
Technically, since its just for type safety, I can do this
t: MyInterface = {};

Jasmine Spec as Typescript File

I'm attempting to set up unit testing in my project, using Jasmine. I am writing my specs in Typescript. My first test is simply checking that a config file returns a value as expected. However, when I import the config, Jasmine can't find the spec. If I take out the import and fill in dummy values, everything works fine.
My spec file is:
/// <reference path="../typings/index.d.ts"/>
process.env.ENV = "test";
process.env.TEST_DB_NAME= "test";
import environment = require("../config/config");
describe("Config Tests:", () => {
it("db returns string", () => {
expect(environment.db).toEqual(process.env.TEST_DB_NAME);
});
});
environment.db should simply return my process.env.TEST_DB_NAME.
I feel this has to do something with the import at the beginning making Jasmine not find the describe(). Anyone know of a way to get Jasmine to work with imports or am I just going about testing this the wrong way?
If you call require directly in your file I think you need to create a module and export it. Another way that I have used import successfully has been to create an interface, export it, and then did something like this.
import IUser = UserList.Interfaces.IUser;
You can then use this as the type for a mock object.

How can I use sinon to spy on a function that is imported into an ember-cli app?

I have an ember app built using ember-cli conventions and I would like to spy on a util function using sinon. The problem is I don't know how to reference the object that the function is defined on.
I have a function defined as follows in app/utils/report-error.js:
reportError = function(err) {
# ...
};
export default reportError;
And then I import that function elsewhere as follows:
import reportError from '../utils/report-error';
Is it possible to spy on this function using sinon? Normally I would refer to the object on which the function is defined:
sandbox = sinon.sandbox.create();
sandbox.spy(someObject, "reportError");
But what is the someObject of a bare function that gets imported?
I realize I could just put the function on an object and import the object, but that means that everywhere the function is used, I need to reference the receiver object too, which seems messy.
When the I look at the javascript that is produced after the import and export statements are transpiled, there is actually an object with a function on it:
reportError['default']();
But any mentions to reportError get similarly transpiled and there doesn't seem a way to refer to the host object of the dependency.
Thoughts?