I have the following Redux action creator:
export const keyDown = key => (dispatch, getState) => {
const { modifier } = getState().data;
dispatch({ type: KEYDOWN, key });
return handle(modifier, key); // Returns true or false
};
And the following connected ponent:
export const mapDispatchToProps = dispatch => ({
onKeyDown: e => {
if(e.target.tagName === "INPUT") return;
const handledKey = dispatch(keyDown(e.keyCode));
if(handledKey) {
e.preventDefault();
}
}
});
I am trying to write a test to ensure that dispatch
is called with the keyDown
action when the tagName
is anything other than "INPUT"
. This is my test:
import { spy } from "sinon";
import keycode from "keycodes";
import { mapDispatchToProps } from "./connected-ponent";
import { keyDown } from "./actions";
// Creates a simple Event stub...
const createEvent = (tag, keyCode) => ({
target: {
tagName: tag.toUpperCase()
},
preventDefault: spy(),
keyCode
});
it("Dispatches a keyDown event with the specified keyCode if the selected element is not an <input>", () => {
const dispatch = spy();
const keyCode = keycode("u");
mapDispatchToProps(dispatch).onKeyDown(createEvent("div", keyCode));
// This fails...
expect(dispatch).to.have.been.calledWith(keyDown(keycode));
});
Presumably this is something to do with using arrow functions? Is there any way I can ensure that dispatch was called with the function signature that I expect?
I have the following Redux action creator:
export const keyDown = key => (dispatch, getState) => {
const { modifier } = getState().data;
dispatch({ type: KEYDOWN, key });
return handle(modifier, key); // Returns true or false
};
And the following connected ponent:
export const mapDispatchToProps = dispatch => ({
onKeyDown: e => {
if(e.target.tagName === "INPUT") return;
const handledKey = dispatch(keyDown(e.keyCode));
if(handledKey) {
e.preventDefault();
}
}
});
I am trying to write a test to ensure that dispatch
is called with the keyDown
action when the tagName
is anything other than "INPUT"
. This is my test:
import { spy } from "sinon";
import keycode from "keycodes";
import { mapDispatchToProps } from "./connected-ponent";
import { keyDown } from "./actions";
// Creates a simple Event stub...
const createEvent = (tag, keyCode) => ({
target: {
tagName: tag.toUpperCase()
},
preventDefault: spy(),
keyCode
});
it("Dispatches a keyDown event with the specified keyCode if the selected element is not an <input>", () => {
const dispatch = spy();
const keyCode = keycode("u");
mapDispatchToProps(dispatch).onKeyDown(createEvent("div", keyCode));
// This fails...
expect(dispatch).to.have.been.calledWith(keyDown(keycode));
});
Presumably this is something to do with using arrow functions? Is there any way I can ensure that dispatch was called with the function signature that I expect?
Share Improve this question asked Jan 13, 2017 at 11:12 CodingIntrigueCodingIntrigue 78.7k32 gold badges176 silver badges177 bronze badges 2- So are you testing that string equality and the return statement works, or that a developer didn't accidentally remove it? God I dislike most unit tests :( – nanobar Commented Jan 16, 2017 at 14:18
-
Primarily that the
dispatch
is actually called. A lot of times I call the action creator without passing to dispatch. Checking that the keyDown action is passed important too, soexpect(dispatch).to.have.been.called
wouldn't be enough I don't think – CodingIntrigue Commented Jan 16, 2017 at 14:20
3 Answers
Reset to default 3 +500Simplest solution might be to memoize keyDown()
as suggested in another answer (+1). Here's a different approach that attempts to cover all bases...
Since keyDown()
is imported from actions
, we could stub
the function to return
a dummy
value whenever it gets called with keyCode
:
import * as actions;
keyDown = stub(actions, "keyDown");
keyDown.withArgs(keyCode).returns(dummy);
Then, our unit tests would verify dispatch
was called with the dummy
that we had previously setup. We know the dummy
can only be returned by our stubbed keyDown()
, so this check also verifies that keyDown()
was called.
mapDispatchToProps(dispatch).onKeyDown(createEvent("div", keyCode));
expect(dispatch).to.have.been.calledWith(dummy);
expect(keyDown).to.have.been.calledWithExactly(keyCode);
To be thorough, we should add unit tests to confirm that the key event is not dispatched when the target is an <input>
.
mapDispatchToProps(dispatch).onKeyDown(createEvent("input", keyCode));
expect(dispatch).to.not.have.been.called;
expect(keyDown).to.not.have.been.called;
We should also test keyDown()
itself in isolation by verifying that the given dispatch
callback is invoked with the correct key event and key
code:
expect(dispatch).to.have.been.calledWith({type: actions.KEYDOWN, key: keyCode});
Links
- GitHub demo
- 100% test coverage
As @DarkKnight said (got +1), keyDown
is returning a new function for every invocation, so the test fails because keyDown(keyCode)
!= keyDown(keyCode)
.
If you don't want to change your actual implementation of keyDown
, you can just mock in your tests:
import * as actions from "./actions";
spyOn(actions, 'keyDown');
You can see other answers on how it can be done:
How to mock the imports of an ES6 module?
How to mock dependencies for unit tests with ES6 Modules
keyDown(keycode)
creates a new function every time, and every function instances are different, the test case fails as expected.
This can be fixed by memorize functions created by keyDown
:
let cacheKeyDown = {};
export const keyDown = key => cacheKeyDown[key] || cacheKeyDown[key] = (dispatch, getState) => {
const { modifier } = getState().data;
dispatch({ type: KEYDOWN, key });
return handle(modifier, key);
};
With memorization, keyDown
calls with same keycode
return the same function.