Reputation: 1676
I am trying to wrap my head around testing Flux and React applications using Jest.
I started using Scotch.io tutorial as my starting point and will be creating a full test suite to gain knowledge in how to structure my first real react and flux app.
You can find my code on github to see where I am at.
The test that does not run is FluxProduct-test.jsx
jest.dontMock('../FluxProduct');
var React
, TestUtils
, FluxProduct
, FluxCartActions;
describe('FluxProduct', function() {
var SAMPLE = {
product: {
id: '1',
name: 'Name',
image: 'image.png',
description: 'this description',
variants: [
{
sku: '123',
type: 'First',
price: 1.99,
inventory: 1
},
{
sku: '456',
type: 'Second',
price: 2.99,
inventory: 3
},
{
sku: '789',
type: 'Third',
price: 3.99,
inventory: 2
}
]
},
};
function getElement(product, className)
{
return product.getDOMNode().getElementsByClassName(className)[0];
}
function getElementByTag(product, tagName)
{
return product.getDOMNode().getElementsByTagName(tagName)[0];
}
function selectIsActive(select, text)
{
for( var i = 0; i < select.options.length; i++)
{
if (select.options[i].textContent == text)
{
return true
}
}
return false;
}
beforeEach(function(){
React = require('react/addons')
, TestUtils = React.addons.TestUtils
, FluxProduct = require('../FluxProduct')
, FluxCartActions = require('../../actions/FluxCartActions');
});
it('should have the display all of the fields', function() {
var cartItems = [];
var selected = SAMPLE.product.variants[1];
var product = TestUtils.renderIntoDocument(
<FluxProduct selected={selected} product={SAMPLE.product} cartitems={cartItems} />
);
expect(getElement(product, 'name').textContent).toEqual(SAMPLE.product.name);
expect(getElement(product, 'description').textContent).toEqual(SAMPLE.product.description);
expect(getElement(product, 'price').textContent).toEqual('Price: $' + selected.price);
expect(selectIsActive(getElementByTag(product, 'select'), selected.type)).toEqual(true);
});
it('should allow to add another variant', function() {
var cartItems = [];
var selected = SAMPLE.product.variants[1];
var targetVariantIndex = 2;
var targetVariant = SAMPLE.product.variants[targetVariantIndex];
var product = TestUtils.renderIntoDocument(
<FluxProduct selected={selected} product={SAMPLE.product} cartitems={cartItems} />
);
var selectElement = getElementByTag(product, 'select');
var addToCartBtn = getElementByTag(product, 'select');
TestUtils.Simulate.change(selectElement, { target: { value: targetVariantIndex } });
expect(selectIsActive(selectElement, targetVariant.type)).toEqual(true);
TestUtils.Simulate.click(addToCartBtn);
expect(FluxCartActions.addToCart.mock.calls.length).toBe(1);
expect(FluxCartActions.addToCart.mock.calls[0][0]).toBe(targetVariant.sku);
expect(FluxCartActions.addToCart.mock.calls[0][0]).toBe({
name: targetVariant.name,
type: targetVariant.type,
price: targetVariant.price
});
});
});
It comes back with "TypeError: Cannot read property 'calls' of undefined" on line 100.
When I log out FluxActions it seems that it is not being automocked, which is the reason mock is undefined and accessing the calls property is throwing the error.
fyi: Jest requires Node 0.10, does not run on 0.12
Helpful reference files:
var React = require('react');
var FluxCartActions = require('../actions/FluxCartActions');
// Flux product view
var FluxProduct = React.createClass({
// Add item to cart via Actions
addToCart: function(event){
var sku = this.props.selected.sku;
var update = {
name: this.props.product.name,
type: this.props.selected.type,
price: this.props.selected.price
}
FluxCartActions.addToCart(sku, update);
FluxCartActions.updateCartVisible(true);
},
// Select product variation via Actions
selectVariant: function(event){
FluxCartActions.selectProduct(event.target.value);
},
// Render product View
render: function() {
var ats = (this.props.selected.sku in this.props.cartitems) ?
this.props.selected.inventory - this.props.cartitems[this.props.selected.sku].quantity :
this.props.selected.inventory;
return (
<div className="flux-product">
<img src={'assets/' + this.props.product.image}/>
<div className="flux-product-detail">
<h1 className="name">{this.props.product.name}</h1>
<p className="description">{this.props.product.description}</p>
<p className="price">Price: ${this.props.selected.price}</p>
<select onChange={this.selectVariant}>
{this.props.product.variants.map(function(variant, index){
return (
<option key={index} value={index}>{variant.type}</option>
)
})}
</select>
<button type="button" onClick={this.addToCart} disabled={ats > 0 ? '' : 'disabled'}>
{ats > 0 ? 'Add To Cart' : 'Sold Out'}
</button>
</div>
</div>
);
},
});
module.exports = FluxProduct;
var AppDispatcher = require('../dispatcher/AppDispatcher');
var FluxCartConstants = require('../constants/FluxCartConstants');
// Define action methods
var FluxCartActions = {
// Receive inital product data
receiveProduct: function(data) {
AppDispatcher.handleAction({
actionType: FluxCartConstants.RECEIVE_DATA,
data: data
})
},
// Set currently selected product variation
selectProduct: function(index) {
AppDispatcher.handleAction({
actionType: FluxCartConstants.SELECT_PRODUCT,
data: index
})
},
// Add item to cart
addToCart: function(sku, update) {
AppDispatcher.handleAction({
actionType: FluxCartConstants.CART_ADD,
sku: sku,
update: update
})
},
// Remove item from cart
removeFromCart: function(sku) {
AppDispatcher.handleAction({
actionType: FluxCartConstants.CART_REMOVE,
sku: sku
})
},
// Update cart visibility status
updateCartVisible: function(cartVisible) {
AppDispatcher.handleAction({
actionType: FluxCartConstants.CART_VISIBLE,
cartVisible: cartVisible
})
}
};
module.exports = FluxCartActions;
Here is my package.json file:
{
"name": "flux-pricing",
"version": "0.0.1",
"description": "Pricing component with flux",
"main": "app/assets/javascripts/cart.js",
"dependencies": {
"flux": "^2.0.0",
"react": "^0.12.0",
"underscore": "^1.7.0"
},
"devDependencies": {
"browserify": "~>6.3.0",
"envify": "~3.0.0",
"jest-cli": "^0.4.0",
"react-tools": "^0.12.2",
"reactify": "^1.0",
"watchify": "~2.1.0"
},
"scripts": {
"start": "watchify -o app/assets/javascripts/app.js -v -d .",
"build": "browserify . | uglifyjs -cm > app/assets/javascripts/app.min.js",
"test": "jest"
},
"jest": {
"rootDir": "app/assets/javascripts",
"scriptPreprocessor": "<rootDir>/__tests__/preprocessor.js",
"testFileExtensions": [
"js",
"jsx"
],
"unmockedModulePathPatterns": ["react"],
"testPathIgnorePatterns": [
"preprocessor.js",
"node_modules"
]
},
"browserify": {
"transform": [
"reactify",
"envify"
]
}
}
I run tests by running npm test
in the app directory.
Upvotes: 2
Views: 2903
Reputation: 8615
I think the issue could be that you are requiring the mocks in beforeEach
, which could be un-doing the auto-genMockFunction
that jest
does for you. I just changed some tests I had written to require the modules in the beforeEach
the same way, and they broke as well, as if the functions weren't mocks.
Try just requiring the dependencies once outside the tests.
var React = require('react/addons')
, TestUtils = React.addons.TestUtils
, FluxProduct = require('../FluxProduct')
, FluxCartActions = require('../../actions/FluxCartActions');
describe(...);
Presumably, you are doing this to "reset" the mocks so that tests don't have side-effects, but I'd be surprised if jest
doesn't already handle that for you.
Upvotes: 2
Reputation: 319
Yeah I had the same problem not so long ago. Whenever I trigger something from an event the automatic mocking of Jest doesn't seem to work. Maybe it is worth to open an issue on Github.
In the mean time I solved the problem by manual mocking the action at the top of the page:
jest.mock('../../actions/FluxCartActions');
Not a very elegant solution within Jest but it works. Also I think that you meant this:
var addToCartBtn = getElementByTag(product, 'button');
instead of
var addToCartBtn = getElementByTag(product, 'select');
So you can simulate the click of the button and call addToCart.
Upvotes: 1