ORIGINAL QUESTION
I'm following the example for writing tests for async action creators spelled out in the Redux documentation. I'm following the example as closely as possible, but I can't get the test to work. I'm getting the following error message:
TypeError: Cannot read property 'then' of undefined
(node:789) UnhandledPromiseRejectionWarning: Unhandled promise rejection
(rejection id: 28): TypeError: Cannot read property 'data' of undefined
Here is the code for my action creator and test:
actions/index.js
import axios from 'axios';
import { browserHistory } from 'react-router';
import { AUTH_USER, AUTH_ERROR, RESET_AUTH_ERROR } from './types';
const API_HOST = process.env.NODE_ENV == 'production'
? http://production-server
: 'http://localhost:3090';
export function activateUser(token) {
return function(dispatch) {
axios.put(`${API_HOST}/activations/${token}`)
.then(response => {
dispatch({ type: AUTH_USER });
localStorage.setItem('token', response.data.token);
})
.catch(error => {
dispatch(authError(error.response.data.error));
});
}
}
export function authError(error) {
return {
type: AUTH_ERROR,
payload: error
}
}
confirmation_test.js
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import * as actions from '../../src/actions';
import { AUTH_USER, AUTH_ERROR, RESET_AUTH_ERROR } from
'../../src/actions/types';
import nock from 'nock';
import { expect } from 'chai';
const middlewares = [ thunk ];
const mockStore = configureMockStore(middlewares);
describe('Confirmation_Token action creator', () => {
afterEach(() => {
nock.cleanAll()
});
it('dispatches AUTH_USER', (done) => {
nock('http://localhost:3090')
.put('/activations/123456')
.reply(200, {
token: 7891011
});
const expectedActions = { type: AUTH_USER };
const store = mockStore({});
return store.dispatch(actions.activateUser(123456))
.then(() => { // return of async actions
expect(store.getActions()).toEqual(expectedActions);
done();
});
});
});
UPDATED QUESTION
I've partially (though not entirely) figured this out. I got this to work by adding a return
statement in front of axios
and commenting out the localstorage.setItem
call.
I also turned the object I assigned to expectedActions
to an array, and changed my assertion from toEqual
to to.deep.equal
. Here is the modified code:
actions/index.js
export function activateUser(token) {
return function(dispatch) { // added return statement
return axios.put(`${API_HOST}/activations/${token}`)
.then(response => {
dispatch({ type: AUTH_USER });
// localStorage.setItem('token', response.data.token); Had to comment out local storage
})
.catch(error => {
dispatch(authError(error.response.data.error));
});
}
}
confirmation_test.js
describe('ConfirmationToken action creator', () => {
afterEach(() => {
nock.cleanAll()
});
it('dispatches AUTH_USER', (done) => {
nock('http://localhost:3090')
.put('/activations/123456')
.reply(200, {
token: 7891011
});
const expectedActions = [{ type: AUTH_USER }];
const store = mockStore({});
return store.dispatch(actions.activateUser(123456))
.then(() => { // return of async actions
expect(store.getActions()).to.deep.equal(expectedActions);
done();
});
});
});
But now I can't test localStorage.setItem
without producing this error message:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called
in this test.
Is this because I need to mock out localStorage.setItem
? Or is there an easier solve that I'm missing?
I figured out the solution. It involves the changes I made in my updated question as well as adding a mock of localStorage to my test_helper.js file. Since there seems to be a lot of questions about this online, I figured perhaps my solution could help someone down the line.
test_helper.js
import jsdom from 'jsdom';
global.localStorage = storageMock();
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = global.document.defaultView;
global.navigator = global.window.navigator;
global.window.localStorage = global.localStorage;
// localStorage mock
function storageMock() {
var storage = {};
return {
setItem: function(key, value) {
storage[key] = value || '';
},
getItem: function(key) {
return key in storage ? storage[key] : null;
},
removeItem: function(key) {
delete storage[key];
}
};
}
actions.index.js
export function activateUser(token) {
return function(dispatch) {
return axios.put(`${API_HOST}/activations/${token}`)
.then(response => {
dispatch({ type: AUTH_USER });
localStorage.setItem('token', response.data.token);
})
.catch(error => {
dispatch(authError(error.response.data.error));
});
}
}
confirmation_test.js
describe('Confirmation action creator', () => {
afterEach(() => {
nock.cleanAll()
});
it('dispatches AUTH_USER and stores token in localStorage', (done) => {
nock('http://localhost:3090')
.put('/activations/123456')
.reply(200, {
token: '7891011'
});
const expectedActions = [{ type: AUTH_USER }];
const store = mockStore({});
return store.dispatch(actions.activateUser(123456))
.then(() => { // return of async actions
expect(store.getActions()).to.deep.equal(expectedActions);
expect(localStorage.getItem('token')).to.equal('7891011');
done();
});
});
});