Skip to content

Commit 16ddbce

Browse files
committed
Reducer unit tests cover most actions.
1 parent d21b048 commit 16ddbce

File tree

3 files changed

+244
-30
lines changed

3 files changed

+244
-30
lines changed

test/setup.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
/* eslint-disable no-unused-vars */
22
process.env.NODE_ENV = 'test'
33

4-
var chai = global.chai = require('chai')
4+
var chai = require('chai')
5+
var sinon = require('sinon')
56
var chaiAsPromised = require('chai-as-promised')
67
var sinonChai = require('sinon-chai')
8+
var jsdom = require('jsdom').jsdom
9+
10+
// Chai Plugins
711
chai.use(chaiAsPromised)
812
chai.use(sinonChai)
9-
var expect = global.expect = chai.expect
10-
global.sinon = require('sinon')
11-
var jsdom = require('jsdom').jsdom
13+
14+
// globals
15+
global.expect = chai.expect
16+
global.sinon = sinon
17+
global.chai = chai
1218
global.document = jsdom('<!doctype html><html><body></body></html>')
1319
global.window = document.defaultView
1420
global.navigator = global.window.navigator
@@ -22,6 +28,7 @@ var fbConfig = global.fbConfig = {
2228
storageBucket: 'redux-firebasev3.appspot.com',
2329
messagingSenderId: '823357791673'
2430
}
31+
2532
// Swallow firebase reinitialize error (useful when using watch)
2633
try {
2734
Firebase.initializeApp(fbConfig)

test/unit/actions/auth.spec.js

Lines changed: 125 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import {
1212
createUser,
1313
resetPassword
1414
} from '../../../src/actions/auth'
15-
15+
let functionSpy
16+
let dispatchSpy
1617
const dispatch = () => {
1718
console.log('dispatch')
1819
}
1920

20-
const firebase = {
21+
const fakeFirebase = {
2122
_: {
2223
authUid: '123',
2324
config: {
@@ -34,12 +35,23 @@ const firebase = {
3435
})
3536
}),
3637
auth: () => ({
37-
onAuthStateChanged: () => {
38-
38+
onAuthStateChanged: (f) => {
39+
f({uid: 'asdfasdf'})
3940
},
40-
signOut: () => {},
41-
createUserWithEmailAndPassword: () => Promise.resolve(),
42-
sendPasswordResetEmail: () => Promise.resolve()
41+
signOut: () =>
42+
Promise.resolve({}),
43+
createUserWithEmailAndPassword: () =>
44+
Promise.resolve({ uid: '123', email: '[email protected]', providerData: [{}] }),
45+
signInWithCustomToken: () => {
46+
return Promise.resolve({
47+
toJSON: () => ({
48+
stsTokenManager: {
49+
accessToken: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjbGFpbXMiOnsiZGlzcGxheU5hbWUiOiJFZHdhcmQgV3UiLCJlbWFpbCI6ImVkZGlld3U4MEBnbWFpbC5jb20iLCJvcmlnaW5hbElkIjoiSlFYQ2dRTkxEU1lSMFEzdjlwY3FjTDZJeGRUMiIsInByb3ZpZGVyRGF0YSI6W3siZGlzcGxheU5hbWUiOiJFZHdhcmQgV3UiLCJlbWFpbCI6ImVkZGlld3U4MEBnbWFpbC5jb20ifV19LCJ1aWQiOiJqaTh3c1BDVW5PYUhvUGZBalNCS2ZReU1pTmkxIiwiaWF0IjoxNDc1NDM3MDMyLCJleHAiOjE0NzU0NDA2MzIsImF1ZCI6Imh0dHBzOi8vaWRlbnRpdHl0b29sa2l0Lmdvb2dsZWFwaXMuY29tL2dvb2dsZS5pZGVudGl0eS5pZGVudGl0eXRvb2xraXQudjEuSWRlbnRpdHlUb29sa2l0IiwiaXNzIjoicmVzaWRlLXByb2RAcmVzaWRlLXByb2QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiJyZXNpZGUtcHJvZEByZXNpZGUtcHJvZC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSJ9.aOXOCCAL-lI5AVnd8MVlc86exvCGNySq8X7DM4Gr7PG0ek5mh_8qnFfLuzw2gfv6mHNVgY2UngUmG0qETaBdox7l3wBo1GdP9hB1bM8NltCYffXwxyw7sN36BFWD3l-cz4rlxfmfzosCLj3XtDK8ARDQ76pAXxsK-rRBvMG6N-wgE_ZLf17FVvwB95e1DAmL39fp6dRVxoPflG--m4QEKVk8xIeDx4ol9HJw512gMGtTkRDMEPWVJEdaEAp6L6Lo2-Bk-TxBCHs8gpb7b7eidWMUEXObk0UPQIz2DRh-3olbruimzL_SgPNg4Pz0uUYSn11-Mx_HxxiVtyQj1ufoLA'
50+
},
51+
uid: 'asdfasdfsdf'
52+
})
53+
})
54+
}
4355
})
4456
}
4557

@@ -49,51 +61,142 @@ describe('Actions: Auth', () => {
4961
expect(dispatchLoginError(dispatch, { some: 'error' }))
5062
})
5163
})
64+
5265
describe('dispatchUnauthorizedError', () => {
5366
it('calls dispatch with error', () => {
5467
expect(dispatchUnauthorizedError(dispatch, { some: 'error' }))
5568
})
5669
})
70+
5771
describe('dispatchLogin', () => {
5872
it('calls dispatch', () => {
5973
expect(dispatchLogin(dispatch, { some: 'error' }))
6074
})
6175
})
76+
6277
describe('init', () => {
63-
it.skip('calls firebases onAuthStateChanged', () => {
64-
// TODO: Check that mock onAuthStateChanged function is called using spy
65-
expect(init(dispatch, firebase))
78+
it('calls firebases onAuthStateChanged', () => {
79+
init(dispatch, fakeFirebase)
6680
})
6781
})
82+
6883
describe('unWatchUserProfile', () => {
6984
it('calls profile unwatch', () => {
7085
expect(unWatchUserProfile(firebase))
7186
})
7287
})
88+
7389
describe('watchUserProfile', () => {
7490
it('calls profile unwatch', () => {
75-
expect(watchUserProfile(dispatch, firebase))
91+
watchUserProfile(dispatch, fakeFirebase)
92+
expect(firebase._.profileWatch).to.be.a.function
93+
})
94+
it('sets profile watch function', () => {
95+
watchUserProfile(dispatch, firebase)
96+
expect(firebase._.profileWatch).to.be.a.function
7697
})
7798
})
99+
100+
describe('createUserProfile', () => {
101+
it('creates profile if config is enabled', () => {
102+
return createUserProfile(dispatch, firebase, { uid: '123', email: '[email protected]', providerData: [{}] }, { some: 'asdf' })
103+
.then((profile) => {
104+
expect(profile).to.be.an.object
105+
})
106+
}, 4000)
107+
})
108+
78109
describe('login', () => {
79-
// skipped due to TypeError: Cannot read property 'apply' of undefined
80-
it.skip('logs user into Firebase', () => {
81-
expect(login(dispatch, firebase, { email: '[email protected]', password: 'asdfasdf' }))
82-
})
110+
it('handles invalid email login', () => {
111+
return login(dispatch, firebase, { email: '[email protected]', password: 'asdfasdf' })
112+
.catch((err) => {
113+
expect(err.code).to.equal('auth/user-not-found')
114+
})
115+
}, 4000)
116+
it('handles invalid token login', () => {
117+
return login(dispatch, firebase, { token: '[email protected]' })
118+
.catch((err) => {
119+
expect(err.code).to.equal('auth/invalid-custom-token')
120+
})
121+
}, 4000)
122+
it('handles token login', () => {
123+
const token = 'asdfasdf'
124+
return login(dispatch, fakeFirebase, { token }, { uid: 'asdfasdf' })
125+
.then((authData) => {
126+
expect(authData).to.be.an.object
127+
})
128+
}, 4000)
83129
})
130+
84131
describe('logout', () => {
85-
it('logs user out of Firebase', () => {
86-
expect(logout(dispatch, firebase)).to.eventually.be.fullfilled
132+
beforeEach(() => {
133+
functionSpy = sinon.spy(firebase.auth(), 'signOut')
134+
})
135+
afterEach(() => {
136+
firebase.auth().signOut.restore()
137+
})
138+
it('calls firebase.auth().signOut()', () => {
139+
return logout(dispatch, firebase)
140+
.then(() => {
141+
expect(functionSpy).to.have.been.calledOnce
142+
})
143+
})
144+
it('calls firebase.auth().signOut()', () => {
145+
logout(dispatch, firebase)
146+
expect(functionSpy).to.have.been.calledOnce
147+
})
148+
it('sets authUid to null', () => {
149+
fakeFirebase._.authUid = 'asdfasdf'
150+
return logout(dispatch, fakeFirebase)
151+
.then(() => {
152+
expect(fakeFirebase._.authUid).to.be.null
153+
})
154+
})
155+
it.skip('calls dispatch', () => {
156+
dispatchSpy = sinon.spy(dispatch)
157+
return logout(dispatch, firebase)
158+
.then(() => {
159+
expect(dispatchSpy).to.have.been.calledOnce
160+
})
87161
})
88162
})
163+
89164
describe('createUser', () => {
90-
it('creates user', () => {
91-
expect(createUser(dispatch, firebase, { email: '[email protected]', password: 'asdf' }))
165+
// Skipped because of TypeError: Cannot read property 'apply' of undefined
166+
it.skip('creates user', () => {
167+
return createUser(dispatch, fakeFirebase, { email: '[email protected]', password: 'asdf' }, { email: '[email protected]', password: 'asdf' })
168+
.then(userData => {
169+
expect(userData).to.be.an.object
170+
})
92171
})
93-
})
172+
it('handles no email', () => {
173+
return createUser(dispatch, fakeFirebase, { password: 'asdf' })
174+
.catch((err) => {
175+
expect(err).to.be.a.string
176+
})
177+
})
178+
it('handles no password', () => {
179+
return createUser(dispatch, fakeFirebase, { email: '[email protected]' })
180+
.catch((err) => {
181+
expect(err).to.be.a.string
182+
})
183+
})
184+
}, 4000)
185+
94186
describe('resetPassword', () => {
95-
it('calls to reset user password', () => {
96-
expect(resetPassword(dispatch, firebase, '[email protected]')).to.eventually.be.fulfilled
187+
beforeEach(() => {
188+
functionSpy = sinon.spy(firebase.auth(), 'sendPasswordResetEmail')
189+
})
190+
afterEach(() => {
191+
firebase.auth().sendPasswordResetEmail.restore()
97192
})
193+
it('dispatches error for invalid user', () => {
194+
return resetPassword(dispatch, firebase, '[email protected]')
195+
.catch((err) => {
196+
console.log('error', err)
197+
expect(err.code).to.equal('auth/user-not-found')
198+
expect(functionSpy).to.have.been.calledOnce
199+
})
200+
}, 4000)
98201
})
99202
})

test/unit/reducer.spec.js

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,117 @@
11
/* global describe expect it */
2+
import { fromJS } from 'immutable'
23
import { firebaseStateReducer } from '../../src'
3-
// import { actionTypes } from '../../src/constants'
4-
const exampleState = {}
4+
import { actionTypes } from '../../src/constants'
5+
const emptyState = {
6+
auth: undefined,
7+
authError: undefined,
8+
profile: undefined,
9+
isInitializing: undefined,
10+
data: {}
11+
}
12+
const intializedState = { isInitializing: true, data: {} }
13+
const noError = {"authError":null}
14+
const noAuth = { auth: null, profile: null }
15+
const exampleData = { some: 'data' }
16+
const externalState = {data: { asdfasdf: {} }}
17+
const exampleState = fromJS({})
518

619
describe('reducer', () => {
720
it('is a function', () => {
8-
expect(firebaseStateReducer).to.be.a.function
21+
expect(firebaseStateReducer)
22+
.to.be.a.function
923
})
24+
1025
it('returns state by default', () => {
11-
expect(firebaseStateReducer(exampleState)).to.equal(exampleState)
26+
expect(firebaseStateReducer(exampleData))
27+
.to.equal(exampleData)
28+
})
29+
30+
describe('SET action', () => {
31+
it('handles SET action', () => {
32+
expect(
33+
firebaseStateReducer(
34+
exampleState,
35+
{ type: actionTypes.SET, path: 'asdfasdf' }
36+
)
37+
).to.equal(exampleState)
38+
})
39+
})
40+
41+
describe('NO_VALUE action', () => {
42+
it('sets state', () => {
43+
expect(
44+
JSON.stringify(firebaseStateReducer(
45+
exampleState,
46+
{ type: actionTypes.NO_VALUE, path: 'asdfasdf' }
47+
).toJS())
48+
).to.equal(JSON.stringify(externalState))
49+
})
50+
})
51+
52+
describe('SET_PROFILE action', () => {
53+
it('sets state', () => {
54+
const profile = { email: '[email protected]' }
55+
expect(
56+
JSON.stringify(firebaseStateReducer(
57+
exampleState,
58+
{ type: actionTypes.SET_PROFILE, profile }
59+
).toJS())
60+
).to.equal(JSON.stringify({ profile }))
61+
})
62+
})
63+
64+
describe('LOGOUT action', () => {
65+
it('sets state', () => {
66+
expect(
67+
JSON.stringify(firebaseStateReducer(
68+
exampleState,
69+
{ type: actionTypes.LOGOUT }
70+
).toJS())
71+
).to.equal(JSON.stringify({"auth":null,"authError":null,"profile":null,"isLoading":false,"data":{}}))
72+
})
73+
})
74+
75+
describe('LOGIN action', () => {
76+
it('sets state', () => {
77+
expect(
78+
JSON.stringify(firebaseStateReducer(
79+
exampleState,
80+
{ type: actionTypes.LOGIN }
81+
).toJS())
82+
).to.equal(JSON.stringify(noError))
83+
})
84+
})
85+
86+
describe('LOGIN_ERROR action', () => {
87+
it('sets state', () => {
88+
expect(
89+
JSON.stringify(firebaseStateReducer(
90+
exampleState,
91+
{ type: actionTypes.LOGIN_ERROR }
92+
).toJS())
93+
).to.equal(JSON.stringify(noAuth))
94+
})
95+
})
96+
97+
describe('AUTHENTICATION_INIT_STARTED action', () => {
98+
it('sets state', () => {
99+
expect(
100+
JSON.stringify(firebaseStateReducer(
101+
exampleState,
102+
{ type: actionTypes.AUTHENTICATION_INIT_STARTED }
103+
).toJS())
104+
).to.equal(JSON.stringify(intializedState))
105+
})
106+
})
107+
describe('AUTHENTICATION_INIT_FINISHED action', () => {
108+
it('sets state', () => {
109+
expect(
110+
JSON.stringify(firebaseStateReducer(
111+
exampleState,
112+
{ type: actionTypes.AUTHENTICATION_INIT_STARTED }
113+
).toJS())
114+
).to.equal(JSON.stringify(intializedState))
115+
})
12116
})
13117
})

0 commit comments

Comments
 (0)