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)
Related
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.
So I have a function with a sort that I am trying to unit test with Jasmine.
loadData() {
this.Service.getAll().subscribe(res => {
res.sort((x) => {
return x.Name, x.Id
});
this.stuff = res;
});
}
From what I have found, because the sort is an array.Prototype, I need to add a spyon the method. I have tried both of the following, but they don't handle it:
spyOn(Array.prototype,'sort').and.callThrough();
spyOn(Array.prototype,'sort');
I'm new to Jasmine, So I assume I am just missing something obvious. How do I handle this?
Thanks
It's a bad idea to spy on a prototype, you should spy on an instance instead. Take a look at this post. Actually, you are trying to test the method implementation, but you should test the class API instead. When you use a class, you don't think about its implementation, usually you even don't know how the class is implemented, it's just a black box. You should test the class the same way, otherwise it will be hard to maintain such tests, you'll have to update tests each time the implementation is changed. The loadData() method changes the object state somehow, just check that the state is changed correctly.
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.
I'm starting with TDD and Laravel. Specifically, I'm starting with routes. I defined some and I defined it badly, so excited as I was with the "new" concept of TDD I wanted to write some test for them.
The idea was to test the routes and only the routes, in isolation, as everything I've readed about TDD recomends. I know I can do a $this->call->('METHOD','something') and test response is OK or whatever, but I would like to know that the right method of the right controller is called.
So, I thought that I could mock the controller. This was my first attempt:
public function test_this_route_work_as_expected_mocking_the_controller()
{
//Create the mock
$drawController = \Mockery::mock('App\Http\Controllers\DrawController');
$drawController->shouldReceive('show')->once();
// Bind instance of my controller to the mock
App::instance('App\Http\Controllers\DrawController', $drawController);
$response = $this->call('GET','/draw/1');
// To see what fails. .env debugging is on
print($response);
}
The route is Route::resource('draw', 'DrawController');, I know it's ok. But method show is not called. In the response it can be seen: "Method Mockery_0_App_Http_Controllers_DrawController::getAfterFilters() does not exist on this mock object". So I tried to:
$drawController->getAfterFilters()->willReturn(array());
But I get:
BadMethodCallException: Method Mockery_0_App_Http_Controllers_DrawController::getAfterFilters() does not exist on this mock object
After some testing, I was able to arrive to this solution:
public function test_this_route_work_as_expected_mocking_the_controller_workaround()
{
//Create the mock
$drawController = \Mockery::mock('App\Http\Controllers\DrawController');
// These are the methods I would like to 'stub' in this mock
$drawController->shouldReceive('getAfterFilters')->atMost(1000)->andReturn(array());
$drawController->shouldReceive('getBeforeFilters')->atMost(1000)->andReturn(array());
$drawController->shouldReceive('getMiddleware')->atMost(1000)->andReturn(array());
// This is where the corresponding method is called. I can assume all is OK if we arrive here with
// the right method name:
// public function callAction($method, $parameters)
$drawController->shouldReceive('callAction')->once()->with('show',Mockery::any());
// Bind instance of my controller to the mock
App::instance('App\Http\Controllers\DrawController', $drawController);
//Act
$response = $this->call('GET','/draw/1');
}
But I would like to change the shouldReceives for willReturns: the atMost(1000) are hurting my eyes. So the questions I have are:
1) Is there a cleaner way to test ONLY the routes in Laravel 5? I mean, the ideal scenario will be one in which the controller doesn't exist but, if the route is ok, the test pases
2) Is it possible to "MockStub" the controllers? What's the better way to do it?
Thank you very much.
I've finally got it. You need a partial mock. It can be done as simple as this (the trick is including an "array" of methods to mock to Mockery::mock):
public function test_this_route_work_as_expected_mocking_partially_the_controller()
{
//Create the mock
$drawController = \Mockery::mock('App\Http\Controllers\DrawController[show]');
$drawController->shouldReceive('show')->once();
// Bind instance of my controller to the mock
App::instance('App\Http\Controllers\DrawController', $drawController);
//Act
$this->call('GET','/draw/1');
}
And, if you create a partial mock of all controllers in setup() method, all route tests can be grouped in a single (or a couple) of TestCases
How do I check if Create was not called without using the Rhino Mocks AssertWasNotCalled method.
Here is the test:
[Test]
public void When_try_to_create_directory_that_already_exits_return_false()
{
var directoryInfoMock = MockRepository.GenerateMock<IDirectoryInfoWrap>();
directoryInfoMock.Stub(x => x.Exists).Return(true);
directoryInfoMock.Expect(x => x.Create());
Assert.AreEqual(false, new DirectoryInfoSample().TryToCreateDirectory(directoryInfoMock));
directoryInfoMock.VerifyAllExpectations();
}
Also, can someone clarify what Stub does.
directoryInfoMock.Stub(x => x.Exists).Return(true);
ensures that any call to the property directoryInfoMock.Exists will return true. But if the property is never call or called many times, it will not cause the test to fail. The purpose of the stub is to provide some meal to your code under test so that it can run normally.
directoryInfoMock.Expect(x => x.Create());
expects that the method directoryInfoMock.Create be called at least once. If not, an exception will be thrown by Rhino.Mocks during the execution of directoryInfoMock.VerifyAllExpectations().
So basically, your unit test should work as expected. What is the output of the test?
UPDATE:
You might want to specify an explicit number of times the method should be called as well. This can be done by using Repeat.x with x is Once(), Twice(), Never(), or Times(N).
directoryInfoMock.Expect(x => x.Create()).Repeat.Never();
This expects that Create is never called. And of course your test will fail if it is actually called.
If you need to make sure that only the methods you expect are called you can consider using strict mocks. Then you will get an exception when a method was called that was not expected on your mock, the only change to your code is when you create your mock:
var directoryInfoMock = MockRepository.GenerateStrictMock<IDirectoryInfoWrap>();
if you know exactly which method shouldn't be called its better to use AssertWasNotCalled (you use it after your test was executed). This way you don't tie your test with your code so closely.