Mocking Observable with Jest - rxjs - unit-testing

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.

Related

Clearing mocks after each test jest

I am learning Jest and I see this clearAllMocks function being used, I then check the docs and the description is simply this:
Clears the mock.calls and mock.instances properties of all mocks.
Equivalent to calling .mockClear() on every mocked function.
Returns the jest object for chaining.
It basically says what you could already figure out by reading the function name. I still can't figure out when should I use this and why is this useful. Could you name an example when this would be good to use?
This can be set in Jest config file which is equivalent to calling jest.clearAllMocks() before each test.
https://jestjs.io/docs/configuration#clearmocks-boolean
// jest.config.js
{
// ...rest
"clearMocks": true
}
jest.clearAllMocks() is often used during tests set up/tear down.
afterEach(() => {
jest.clearAllMocks()
});
Doing so ensures that information is not stored between tests which could lead to false assertions. Let's say that you have a mock function mockFn and you call the function, you can assert that it's been called 1 time. If in another test you call mockFn again but you have not cleared the mock, it would have been called two times now instead of one.

Mock Third party library (Razorpay) in Angular Unit tests?

I am trying to test a function in one of my component which consists following two lines:
this.rzp1 = new Razorpay(orderDetails);
this.rzp1.open();
I am trying to understand how to mock Razorpay in my test cases for this function.
This is how I am declaring Razorpay in my component:
export declare var Razorpay: any;
I have already tried various methods like:
var stub = sinon.createStubInstance(MyConstructor)
Any leads will be helpful.
Why not use jasmine's built in spy facility instead of relying on another library (sinon)?
In your before each block, you can do something like this:
beforeEach(() => {
jasmine.spyOnAllFunctions(Razorypay.prototype);
Razorypay.prototype.open.and.returnValue('foo');
});
You can find more information about spyOnAllFuntions in the documentation.

Sinon stub instance method declared in mapDispatchToProps

New to testing and React Redux, so I may be conflating a few issues here. I will only present one example, but I have tried many different combinations of mount(), shallow(), instance(), stub, spy and more.
Given a component, where setFooData() updates redux state and Foo.props.data:
const mapDispatchToProps = (dispatch, props) => ({
setFooData(fooId, data) {
dispatch(Actions.setFooData(fooId, data));
},
});
...
return (
<div fooId={this.props.fooId}>
<Foo {...fooProps}/>
</div>
);
I would like to write some tests around the conditions under which setFooData() is called, namely conditions in lifecycle methods like componentDidMount() and componentWillReceiveProps().
Because setFooData() involves server calls and more, and because these tests merely concern the view layer and how the component renders as a result of Foo.props.data being set eventually by setFooData(), setFooData() seems like a good candidate for stub.
Therefore, Enzyme's shallow(), rather than mount(), seems appropriate, correct? In any case, when I try to stub setFooData():
let wrapper = return shallow(<Foo {...props}/>);
let stub = sinon.stub(wrapper.instance(), 'setFooData');
I receive the error:
Attempted to wrap undefined property setFooData as function
Upon inspection, wrapper.instance() yields an object where setFooData() is indeed not defined, but according to other examples, I would think it should be.
Furthermore, setFooData() does exist on wrapper.instance().selector.props, and while let stub = sinon.stub(wrapper.instance().selector.props, 'setFooData'); avoids the error, when I inspect the object setFooData() =/= stub, and the function is not called as per the test.
When I use mount() instead,
let wrapper = mount(<Provider store={store}><Foo {...props}/></Provider>);
let componentDidMountSpy = sinon.spy(Foo.prototype, 'componentDidMount');
let componentWillReceivePropsSpy = sinon.spy(Foo.prototype, 'componentWillReceiveProps');
expect(componentDidMountSpy.called).to.be.true; //passes
expect(componentWillReceivePropsSpy.called).to.be.true; //passes
expect(stub.called).to.be.true; //fails
I receive a different error that appears related to the body of setFooData(), so setFooData() is called but the function is not actually stubbed to prevent its real body from being executed.
Thanks for any help to clarify my understanding.
I think you're taking the hardest path. You should test your component in isolation, not the connected one. If you test the connected component you're making an integration test and double testing that connect indeed works. That's already tested in react-redux for you.
Instead, test your action creators by themselves in unit tests.
Then, export your component as named export without connecting and use the default export for the connect version.
That way you can simply import the pure-React version and pass anything you want as props, in order to make easy assertions afterwards.
If you need to specifically test that something happens in those lifecycle methods, you can call those methods from the instance:
const fakeActionCreator = sinon.spy()
const subject = mount(<MyComponent doSomething={ fakeActionCreator } />)
subject.instance().componentDidMount()
assert.equal(fakeActionCreator.callCount, 1)

Mock Action functions in Jest test

How would one go about calling a function from an imported action module? Here is my component
class Task extends Component {
handleClick(e) {
e.preventDefault();
Actions.returnTask(this.props.id);
}
render() {...}
}
and tests that look like this:
jest.dontMock('./Task');
jest.dontMock('./Actions');
describe('Tasks', function() {
let renderedComponent;
let mockReturnTask;
let TaskActions;
beforeEach(function() {
renderedComponent = TestUtils.renderIntoDocument(
<Task task={testTask} key={1} />
);
Actions = require('./Actions');
mockReturnTask = jest.genMockFn();
Actions.returnTask = mockReturnTask;
});
it('should call a returnTask action on Actions', function() {
renderedComponent.handleClick(event);
expect(mockReturnTask).toBeCalled();
});
});
When running my test it tells me that the function was not called. If I do expect(Actions.returnTask).toBeCalled(); I get an error message that toBeCalledWith() should be used on a mock function error. How do I mock a function on the external Actions and check that it is called in my tests? I feel like my method above should be working.
Your example doesn't include the require of Task but I'm pretty sure of what happened here. Jest users must be aware of what require they do, and when.
Chronologically:
jest is set to never mock Actions and Task
Task component is required, jest requires it for real
Actions is required because it is required in the Task component. jest requires it for real.
Task component is instantiated
Actions.returnTask is monkey-patched but it's not the function the component is bind with, it's a new function who exist only in the test.
To make your component using a jest mocked function, you have to require and monkey-patch Actions before requiring Tasks
Be aware that you don't have to unmocked Actions if you mock a function of Actions right after. It's exactly what the jest auto-mocking killing feature is made for : remove jest.dontMock('./Actions'); and all references to your Actions module. The test should be working.

How do I use Sinon with Typescript?

If I use sinon with typescript then how do I cast the sinon mock to an instance of my object?
For instance a SinonMock would be returned but my controller under test may require a specific service passed in to its constructor.
var myServiceMock: MyStuff.MyService = <MyStuff.MyService (sinon.mock(MyStuff.MyService));
controllerUnderTest = new MyStuff.MyController(myServiceMock, $log);
Can sinon be used with Typescript?
Sinon can create a stub based on a constructor quite easily if, instead of mock, you use the createStubInstance method.
An example using mocha, chai, sinon and sinon-chai, could look like this:
import * as sinon from 'sinon';
import * as chai from 'chai';
// ... imports for the classes under test
const expect = chai.expect;
const sinonChai = require("sinon-chai");
chai.use(sinonChai);
describe('MyController', () => {
it('uses MyService', () => {
let myService = sinon.createStubInstance(MyStuff.MyService),
controller = new MyStuff.MyController(myService as any, ...);
// ... perform an action on the controller
// that calls myService.aMethodWeAreInterestedIn
// verify if the method you're interested in has been called if you want to
expect(myService.aMethodWeAreInterestedIn).to.have.been.called;
});
});
I've published an article, which you might find useful if you'd like to learn more about the different test doubles and how to use them with Sinon.js.
Hope this helps!
Jan
You may need to use an <any> type assertion to make the type wide before you narrow it to your specific type:
var myServiceMock: MyStuff.MyService =
<MyStuff.MyService> <any> (sinon.mock(MyStuff.MyService));
Just to clarify one behaviour of sinon - although you pass in MyStuff.MyService, whatever you pass to the mock method is only used to provide better error messages.
If you want the mock to have methods and properties, you need to add them.
If you want automatically created fakes, you can grab the FakeFactory from tsUnit, which creates a fake version with some default values that you can opt to override - in JavaScript this is pretty easy stuff (plus by not using too much mock functionality, you can ensure you are testing behaviour rather than implementation).
Example use of FakeFactory:
var target = tsUnit.FakeFactory.getFake<RealClass>(RealClass);
var result = target.run();
this.areIdentical(undefined, result);
In Typescript this can be achieved by using sinon.createStubInstance and SinonStubbedInstance class.
Example:
let documentRepository: SinonStubbedInstance<DocumentRepository>;
documentRepository = sinon.createStubInstance(DocumentRepository);
Now you have a full intellisense for working with all stubbed methods of this class.
Example Arrange:
documentRepository.delete.resolves({deletedCount: 1});
documentRepository.update.throws(error);
Example Assert:
sinon.assert.calledOnce(documentRepository.update);
There is only one place where you would need to perform type casting and that is the initiallization of the class you want to unit test.
Example:
documentsController =
new DocumentsController(
userContext,
documentRepository as unknown as DocumentRepository);
Hope this will help.
More on this article.
Use ts-sinon.
It lets you:
stub objects
stub interfaces