Commit 32c9dd52 authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Add tests for more sagas

parent 22b402a5
import { select } from 'redux-saga/effects';
import { expectSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { apiRequest, tokenSelector } from '../../app/url';
import calendarSaga from '../../app/sagas/calendar';
import * as calendarActions from '../../app/actions/calendar';
import * as navActions from '../../app/actions/navigation';
jest.mock('../../app/url', () => ({
apiRequest: jest.fn(() => {}),
tokenSelector: () => 'token',
}));
describe('calendar saga', () => {
const error = new Error('error');
it('should start fetching on refresh', () => expectSaga(calendarSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.fn(apiRequest), []],
])
.dispatch(calendarActions.refresh())
.put(calendarActions.fetching())
.silentRun());
it('should start fetching on navigate', () => expectSaga(calendarSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.fn(apiRequest), []],
])
.dispatch(navActions.navigate('eventList'))
.put(calendarActions.fetching())
.silentRun());
it('should put an error when the api request fails', () => expectSaga(calendarSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.fn(apiRequest), throwError(error)],
])
.dispatch(calendarActions.refresh())
.put(calendarActions.failure())
.silentRun());
it('should put the result data when the request succeeds', () => expectSaga(calendarSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['events'] }), [{ pk: 1 }]],
[matchers.call.like({ fn: apiRequest, args: ['partners/events'] }), [{ pk: 2 }]],
])
.dispatch(calendarActions.refresh())
.put(calendarActions.success([{ pk: 1 }, { pk: -2, partner: true }]))
.silentRun());
it('should do two GET requests', () => expectSaga(calendarSaga)
.provide([
[select(tokenSelector), 'usertoken'],
])
.dispatch(calendarActions.refresh())
.silentRun()
.then(() => {
expect(apiRequest).toBeCalledWith('events', {
headers: {
Accept: 'application/json',
Authorization: 'Token usertoken',
'Content-Type': 'application/json',
},
method: 'GET',
});
expect(apiRequest).toBeCalledWith('partners/events', {
headers: {
Accept: 'application/json',
Authorization: 'Token usertoken',
'Content-Type': 'application/json',
},
method: 'GET',
});
}));
});
import * as matchers from 'redux-saga-test-plan/matchers';
import { select } from 'redux-saga/effects';
import { expectSaga } from 'redux-saga-test-plan';
import deepLinkingSaga, { parseURL } from '../../app/sagas/deepLinking';
import * as deepLinkingActions from '../../app/actions/deepLinking';
import { url as siteURL, apiRequest, loggedInSelector } from '../../app/url';
import * as navigationActions from '../../app/actions/navigation';
import * as eventActions from '../../app/actions/event';
import * as loginActions from '../../app/actions/login';
import * as pizzaActions from '../../app/actions/pizza';
describe('calendar saga', () => {
it('should parse a URL correctly', () => {
const eventsUrl = parseURL(`${siteURL}/events/1`);
expect(eventsUrl).toEqual({ params: {}, path: '/events/1' });
const paramsUrl = parseURL(`${siteURL}/events?id=1`);
expect(paramsUrl).toEqual({ params: { id: '1' }, path: '/events' });
const emptyUrl = parseURL('http://example.org/');
expect(emptyUrl).toEqual({ params: {}, path: '' });
});
it('should not do anything when no url is provided', () => expectSaga(deepLinkingSaga)
.dispatch(deepLinkingActions.deepLink(''))
.not.put.like({ type: 'DEEPLINKING_DEEPLINK' })
.silentRun());
it('should wait for login before processing deeplink', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), false],
[matchers.call.fn(apiRequest), []],
])
.dispatch(deepLinkingActions.deepLink(`${siteURL}/events/1/`))
.dispatch(loginActions.success('', ''))
.put(eventActions.event('1'))
.silentRun());
it('should navigate to eventList on /events/ deeplink', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), true],
[matchers.call.fn(apiRequest), []],
])
.dispatch(deepLinkingActions.deepLink(`${siteURL}/events/`))
.put(navigationActions.navigate('eventList'))
.silentRun());
it('shouldl load event on /events/{id} deeplink', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), true],
[matchers.call.fn(apiRequest), []],
])
.dispatch(deepLinkingActions.deepLink(`${siteURL}/events/1/`))
.put(eventActions.event('1'))
.silentRun());
it('should load pizzas on /pizzas/ deeplink', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), true],
[matchers.call.fn(apiRequest), []],
])
.dispatch(deepLinkingActions.deepLink(`${siteURL}/pizzas/`))
.put(pizzaActions.retrievePizzaInfo())
.silentRun());
});
......@@ -9,8 +9,12 @@ import eventSaga from '../../app/sagas/event';
import * as eventActions from '../../app/actions/event';
import * as navActions from '../../app/actions/navigation';
jest.mock('../../app/url', () => ({
apiRequest: jest.fn(() => {}),
tokenSelector: () => 'token',
}));
describe('event api call', () => {
describe('event saga', () => {
const error = new Error('error');
it('should start fetching', () => expectSaga(eventSaga)
......@@ -49,4 +53,29 @@ describe('event api call', () => {
.dispatch(eventActions.event(1))
.put(eventActions.success('eventData', 'regData'))
.silentRun());
it('should do two GET requests', () => expectSaga(eventSaga)
.provide([
[select(tokenSelector), 'usertoken'],
])
.dispatch(eventActions.event(1))
.silentRun()
.then(() => {
expect(apiRequest).toBeCalledWith('events/1', {
headers: {
Accept: 'application/json',
Authorization: 'Token usertoken',
'Content-Type': 'application/json',
},
method: 'GET',
});
expect(apiRequest).toBeCalledWith('events/1/registrations', {
headers: {
Accept: 'application/json',
Authorization: 'Token usertoken',
'Content-Type': 'application/json',
},
method: 'GET',
}, { status: 'registered' });
}));
});
import { testSaga } from 'redux-saga-test-plan';
import indexSaga from '../../app/sagas/index';
jest.mock('react-native-snackbar', () => ({
LENGTH_LONG: 100,
}));
describe('index saga', () => {
it('should yield to all indexSaga as forks', () => {
testSaga(indexSaga)
.next()
.inspect((fn) => {
expect(fn).toHaveProperty('ALL');
})
.next()
.isDone();
});
});
import { expectSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import Snackbar from 'react-native-snackbar';
import { AsyncStorage } from 'react-native';
import loginSaga, { DISPLAYNAMEKEY, PHOTOKEY, TOKENKEY, USERNAMEKEY } from '../../app/sagas/login';
import { apiRequest } from '../../app/url';
import * as loginActions from '../../app/actions/login';
import * as pushNotificationsActions from '../../app/actions/pushNotifications';
jest.mock('react-native-snackbar', () => ({
LENGTH_LONG: 100,
show: jest.fn(),
dismiss: jest.fn(),
}));
jest.mock('react-native', () => ({
AsyncStorage: {
multiSet: jest.fn(),
multiRemove: jest.fn(),
},
}));
jest.mock('../../app/url', () => ({
apiRequest: jest.fn(() => {}),
tokenSelector: () => 'token',
}));
describe('login saga', () => {
const error = new Error('error');
describe('logging in', () => {
it('should show a snackbar on start', () => expectSaga(loginSaga)
.dispatch(loginActions.login('username', 'password'))
.silentRun()
.then(() => {
expect(Snackbar.show).toBeCalledWith(
{ title: 'Logging in', duration: Snackbar.LENGTH_INDEFINITE });
}));
it('should put the result data when the request succeeds', () => expectSaga(loginSaga)
.provide([
[matchers.call.like({ fn: apiRequest, args: ['token-auth'] }), { token: 'abc123' }],
])
.put(loginActions.success('username', 'abc123'))
.put(loginActions.profile('abc123'))
.dispatch(loginActions.login('username', 'password'))
.silentRun());
it('should show a snackbar when the request succeeds', () => expectSaga(loginSaga)
.provide([
[matchers.call.like({ fn: apiRequest, args: ['token-auth'] }), { token: 'abc123' }],
])
.dispatch(loginActions.login('username', 'password'))
.silentRun()
.then(() => {
expect(Snackbar.dismiss).toBeCalled();
expect(Snackbar.show).toBeCalledWith(
{ title: 'Login successful' });
}));
it('should save the token in the AsyncStorage when the request succeeds', () =>
expectSaga(loginSaga)
.provide([
[matchers.call.like({ fn: apiRequest, args: ['token-auth'] }), { token: 'abc123' }],
])
.dispatch(loginActions.login('username', 'password'))
.silentRun()
.then(() => {
expect(AsyncStorage.multiSet).toBeCalledWith([
[USERNAMEKEY, 'username'],
[TOKENKEY, 'abc123'],
]);
}));
it('should show a snackbar when the request fails', () => expectSaga(loginSaga)
.provide([
[matchers.call.fn(apiRequest), throwError(error)],
])
.dispatch(loginActions.login('username', 'password'))
.silentRun()
.then(() => {
expect(Snackbar.dismiss).toBeCalled();
expect(Snackbar.show).toBeCalledWith(
{ title: 'Login failed' });
}));
it('should do a POST request', () => expectSaga(loginSaga)
.dispatch(loginActions.login('username', 'password'))
.silentRun()
.then(() => {
expect(apiRequest).toBeCalledWith('token-auth', {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
method: 'POST',
body: '{"username":"username","password":"password"}',
});
}));
});
describe('logging out', () => {
it('should remove the token from the AsyncStorage', () => expectSaga(loginSaga)
.dispatch(loginActions.logout())
.silentRun()
.then(() => {
expect(AsyncStorage.multiRemove).toBeCalledWith([USERNAMEKEY, TOKENKEY]);
}));
it('should put a push notification invalidation action', () => expectSaga(loginSaga)
.put(pushNotificationsActions.invalidate())
.dispatch(loginActions.logout())
.silentRun());
it('should remove the token from the AsyncStorage', () => expectSaga(loginSaga)
.dispatch(loginActions.logout())
.silentRun()
.then(() => {
expect(Snackbar.show).toBeCalledWith(
{ title: 'Logout successful' });
}));
});
describe('getting profile', () => {
it('should put the result data when the request succeeds', () => expectSaga(loginSaga)
.provide([
[matchers.call.like({ fn: apiRequest, args: ['members/me'] }), {
display_name: 'Johnny Test',
avatar: {
medium: 'http://example.org/photo.png',
},
}],
])
.put(loginActions.profileSuccess('Johnny Test', 'http://example.org/photo.png'))
.dispatch(loginActions.profile('abc123'))
.silentRun());
it('should save the token in the AsyncStorage when the request succeeds', () => expectSaga(loginSaga)
.provide([
[matchers.call.like({ fn: apiRequest, args: ['members/me'] }), {
display_name: 'Johnny Test',
avatar: {
medium: 'http://example.org/photo.png',
},
}],
])
.dispatch(loginActions.profile('abc123'))
.silentRun()
.then(() => {
expect(AsyncStorage.multiSet).toBeCalledWith([
[DISPLAYNAMEKEY, 'Johnny Test'],
[PHOTOKEY, 'http://example.org/photo.png'],
]);
}));
it('should not care about errors', () => expectSaga(loginSaga)
.provide([
[matchers.call.fn(apiRequest), throwError(error)],
])
.dispatch(loginActions.profile('token'))
.silentRun());
it('should do a GET request', () => expectSaga(loginSaga)
.dispatch(loginActions.profile('abc123'))
.silentRun()
.then(() => {
expect(apiRequest).toBeCalledWith('members/me', {
headers: {
Accept: 'application/json',
Authorization: 'Token abc123',
'Content-Type': 'application/json',
},
method: 'GET',
});
}));
});
});
import * as matchers from 'redux-saga-test-plan/matchers';
import { select } from 'redux-saga/effects';
import { expectSaga } from 'redux-saga-test-plan';
import { throwError } from 'redux-saga-test-plan/providers';
import { apiRequest, tokenSelector } from '../../app/url';
import pizzaSaga from '../../app/sagas/pizza';
import * as pizzaActions from '../../app/actions/pizza';
import * as navigationActions from '../../app/actions/navigation';
jest.mock('../../app/url', () => ({
apiRequest: jest.fn(() => {}),
tokenSelector: () => 'token',
}));
describe('pizza saga', () => {
const error = new Error('error');
error.response = null;
beforeEach(() => {
apiRequest.mockReset();
});
describe('retrieve pizza info', () => {
it('should start fetching and navigate on retrieve action', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.fn(apiRequest), []],
])
.dispatch(pizzaActions.retrievePizzaInfo())
.put(pizzaActions.fetching())
.put(navigationActions.navigate('pizza'))
.silentRun());
describe('failures', () => {
beforeAll(() => {
error.response = null;
});
it('should put failure when the pizza event request fails', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas/event'] }), throwError(error)],
[matchers.call.fn(apiRequest), []],
])
.dispatch(pizzaActions.retrievePizzaInfo())
.put(pizzaActions.failure())
.silentRun());
it('should put failure when the pizzas request fails', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas'] }), throwError(error)],
[matchers.call.fn(apiRequest), []],
])
.dispatch(pizzaActions.retrievePizzaInfo())
.put(pizzaActions.failure())
.silentRun());
it('should put failure when the order request fails', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas/orders/me'] }), throwError(error)],
[matchers.call.fn(apiRequest), []],
])
.dispatch(pizzaActions.retrievePizzaInfo())
.put(pizzaActions.failure())
.silentRun());
});
describe('successes', () => {
beforeAll(() => {
error.response = { status: 404 };
});
it('should put the result data when the requests all succeed', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas/event'] }), 'pizzaEvent'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas'] }), 'pizzaList'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas/orders/me'] }), 'pizzaOrder'],
])
.dispatch(pizzaActions.retrievePizzaInfo())
.put(pizzaActions.success('pizzaEvent', 'pizzaOrder', 'pizzaList'))
.silentRun());
it('should put the result data when the order is not found', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas/event'] }), 'pizzaEvent'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas'] }), 'pizzaList'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas/orders/me'] }), throwError(error)],
])
.dispatch(pizzaActions.retrievePizzaInfo())
.put(pizzaActions.success('pizzaEvent', null, 'pizzaList'))
.silentRun());
it('should put the result data when the pizzas are not found', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas/event'] }), 'pizzaEvent'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas'] }), throwError(error)],
])
.dispatch(pizzaActions.retrievePizzaInfo())
.put(pizzaActions.success(null, null, []))
.silentRun());
});
it('should do three GET requests', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'usertoken'],
])
.dispatch(pizzaActions.retrievePizzaInfo())
.silentRun()
.then(() => {
expect(apiRequest).toBeCalledWith('pizzas/event', {
headers: {
Accept: 'application/json',
Authorization: 'Token usertoken',
'Content-Type': 'application/json',
},
method: 'GET',
});
expect(apiRequest).toBeCalledWith('pizzas', {
headers: {
Accept: 'application/json',
Authorization: 'Token usertoken',
'Content-Type': 'application/json',
},
method: 'GET',
});
expect(apiRequest).toBeCalledWith('pizzas/orders/me', {
headers: {
Accept: 'application/json',
Authorization: 'Token usertoken',
'Content-Type': 'application/json',
},
method: 'GET',
});
}));
});
describe('cancel pizza order', () => {
it('should put success when the request succeeds', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas/orders/me'] }), 'pizzaOrder'],
])
.dispatch(pizzaActions.cancelOrder())
.put(pizzaActions.cancelSuccess())
.silentRun());
it('should put failure when the request fails', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas/orders/me'] }), throwError(error)],
])
.dispatch(pizzaActions.cancelOrder())
.put(pizzaActions.failure())
.silentRun());
it('should do a DELETE request', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
])
.dispatch(pizzaActions.cancelOrder())
.silentRun()
.then(() => {
expect(apiRequest).toBeCalledWith('pizzas/orders/me', {
headers: {
Accept: 'application/json',
Authorization: 'Token token',
'Content-Type': 'application/json',
},
method: 'DELETE',
});
}));
});
describe('create pizza order', () => {
describe('order exists', () => {
it('should put success when the request succeeds', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas/orders/me'] }), 'pizzaOrder'],
])
.dispatch(pizzaActions.orderPizza(1, true))
.put(pizzaActions.orderSuccess('pizzaOrder'))
.silentRun());
it('should put failure when the request fails', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['pizzas/orders/me'] }), throwError(error)],
])
.dispatch(pizzaActions.orderPizza(1, true))
.put(pizzaActions.failure())
.silentRun());
<