Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 617
MLyck Avatar asked Dec 07 '18 10:12

MLyck


2 Answers

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)

like image 98
jesse Avatar answered Oct 13 '22 04:10

jesse


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.

like image 36
Brian Adams Avatar answered Oct 13 '22 03:10

Brian Adams