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 component:
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-component";
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?
Simplest 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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With