From a70ddd79f6196351ee613bb91d36723d4ef3f02b Mon Sep 17 00:00:00 2001 From: Simon Chen Date: Thu, 6 Dec 2018 14:15:25 -0500 Subject: [PATCH] Add more unit tests on actions and reducers --- src/data/actions/assignmentTypes.js | 2 +- src/data/actions/assignmentTypes.test.js | 73 ++++++++++++ src/data/actions/cohorts.js | 2 +- src/data/actions/cohorts.test.js | 5 +- src/data/actions/grades.test.js | 142 +++++++++++++++++++++++ src/data/actions/tracks.js | 2 +- src/data/actions/tracks.test.js | 80 +++++++++++++ src/data/reducers/cohorts.js | 1 + src/data/reducers/cohorts.test.js | 70 +++++++++++ 9 files changed, 372 insertions(+), 5 deletions(-) create mode 100644 src/data/actions/assignmentTypes.test.js create mode 100644 src/data/actions/grades.test.js create mode 100644 src/data/actions/tracks.test.js create mode 100644 src/data/reducers/cohorts.test.js diff --git a/src/data/actions/assignmentTypes.js b/src/data/actions/assignmentTypes.js index 8e58617..2ac795e 100644 --- a/src/data/actions/assignmentTypes.js +++ b/src/data/actions/assignmentTypes.js @@ -17,7 +17,7 @@ const fetchAssignmentTypes = courseId => ( .then((data) => { dispatch(gotAssignmentTypes(Object.keys(data.assignment_types))); }) - .catch((error) => { + .catch(() => { dispatch(errorFetchingAssignmentTypes()); }); } diff --git a/src/data/actions/assignmentTypes.test.js b/src/data/actions/assignmentTypes.test.js new file mode 100644 index 0000000..b13bd55 --- /dev/null +++ b/src/data/actions/assignmentTypes.test.js @@ -0,0 +1,73 @@ +import configureMockStore from 'redux-mock-store'; +import MockAdapter from 'axios-mock-adapter'; +import thunk from 'redux-thunk'; + +import apiClient from '../apiClient'; +import { configuration } from '../../config'; +import { fetchAssignmentTypes } from './assignmentTypes'; +import { + STARTED_FETCHING_ASSIGNMENT_TYPES, + GOT_ASSIGNMENT_TYPES, + ERROR_FETCHING_ASSIGNMENT_TYPES, +} from '../constants/actionTypes/assignmentTypes'; + +const mockStore = configureMockStore([thunk]); +const axiosMock = new MockAdapter(apiClient); + +describe('actions', () => { + afterEach(() => { + axiosMock.reset(); + }); + + describe('fetchAssignmentTypes', () => { + const courseId = 'course-v1:edX+DemoX+Demo_Course'; + + it('dispatches success action after fetching fetchAssignmentTypes', () => { + const responseData = { + assignment_types: { + Exam: { + drop_count: 0, + min_count: 1, + short_label: 'Exam', + type: 'Exam', + weight: 0.25, + }, + Homework: { + drop_count: 1, + min_count: 3, + short_label: 'Ex', + type: 'Homework', + weight: 0.75, + }, + }, + }; + const expectedActions = [ + { type: STARTED_FETCHING_ASSIGNMENT_TYPES }, + { type: GOT_ASSIGNMENT_TYPES, assignmentTypes: Object.keys(responseData.assignment_types) }, + ]; + const store = mockStore(); + + axiosMock.onGet(`${configuration.LMS_BASE_URL}/api/grades/v1/gradebook/${courseId}/grading-info?graded_only=true`) + .replyOnce(200, JSON.stringify(responseData)); + + return store.dispatch(fetchAssignmentTypes(courseId)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + + it('dispatches failure action after fetching cohorts', () => { + const expectedActions = [ + { type: STARTED_FETCHING_ASSIGNMENT_TYPES }, + { type: ERROR_FETCHING_ASSIGNMENT_TYPES }, + ]; + const store = mockStore(); + + axiosMock.onGet(`${configuration.LMS_BASE_URL}/api/grades/v1/gradebook/${courseId}/grading-info?graded_only=true`) + .replyOnce(500, JSON.stringify({})); + + return store.dispatch(fetchAssignmentTypes(courseId)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + }); +}); diff --git a/src/data/actions/cohorts.js b/src/data/actions/cohorts.js index 915c4f6..3d928d6 100644 --- a/src/data/actions/cohorts.js +++ b/src/data/actions/cohorts.js @@ -17,7 +17,7 @@ const fetchCohorts = courseId => ( .then((data) => { dispatch(gotCohorts(data.cohorts)); }) - .catch((error) => { + .catch(() => { dispatch(errorFetchingCohorts()); }); } diff --git a/src/data/actions/cohorts.test.js b/src/data/actions/cohorts.test.js index 0baed3e..ed32d38 100644 --- a/src/data/actions/cohorts.test.js +++ b/src/data/actions/cohorts.test.js @@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter'; import thunk from 'redux-thunk'; import apiClient from '../apiClient'; +import { configuration } from '../../config'; import { fetchCohorts } from './cohorts'; import { STARTED_FETCHING_COHORTS, @@ -47,7 +48,7 @@ describe('actions', () => { ]; const store = mockStore(); - axiosMock.onGet(`http://localhost:18000/courses/${courseId}/cohorts/`) + axiosMock.onGet(`${configuration.LMS_BASE_URL}/courses/${courseId}/cohorts/`) .replyOnce(200, JSON.stringify(responseData)); return store.dispatch(fetchCohorts(courseId)).then(() => { @@ -62,7 +63,7 @@ describe('actions', () => { ]; const store = mockStore(); - axiosMock.onGet(`http://localhost:18000/courses/${courseId}/cohorts/`) + axiosMock.onGet(`${configuration.LMS_BASE_URL}/courses/${courseId}/cohorts/`) .replyOnce(500, JSON.stringify({})); return store.dispatch(fetchCohorts(courseId)).then(() => { diff --git a/src/data/actions/grades.test.js b/src/data/actions/grades.test.js new file mode 100644 index 0000000..6fd7221 --- /dev/null +++ b/src/data/actions/grades.test.js @@ -0,0 +1,142 @@ +import configureMockStore from 'redux-mock-store'; +import MockAdapter from 'axios-mock-adapter'; +import thunk from 'redux-thunk'; + +import apiClient from '../apiClient'; +import { configuration } from '../../config'; +import { fetchGrades } from './grades'; +import { + STARTED_FETCHING_GRADES, + FINISHED_FETCHING_GRADES, + ERROR_FETCHING_GRADES, + GOT_GRADES, + UPDATE_BANNER, +} from '../constants/actionTypes/grades'; +import { sortAlphaAsc } from './utils'; + + +const mockStore = configureMockStore([thunk]); +const axiosMock = new MockAdapter(apiClient); + +describe('actions', () => { + afterEach(() => { + axiosMock.reset(); + }); + + describe('fetchGrades', () => { + const courseId = 'course-v1:edX+DemoX+Demo_Course'; + const expectedCohort = 1; + const expectedTrack = 'verified'; + const fetchGradesURL = `${configuration.LMS_BASE_URL}/api/grades/v1/gradebook/${courseId}/?page_size=10&cohort_id=${expectedCohort}&enrollment_mode=${expectedTrack}`; + const responseData = { + next: `${fetchGradesURL}&cursor=2344fda`, + previous: null, + results: [ + { + course_id: courseId, + email: 'user1@example.com', + username: 'user1', + user_id: 1, + percent: 0.5, + letter_grade: null, + section_breakdown: [ + { + subsection_name: 'Demo Course Overview', + score_earned: 0, + score_possible: 0, + percent: 0, + displayed_value: '0.00', + grade_description: '(0.00/0.00)', + }, + { + subsection_name: 'Example Week 1: Getting Started', + score_earned: 1, + score_possible: 1, + percent: 1, + displayed_value: '1.00', + grade_description: '(0.00/0.00)', + }, + ], + }, + { + course_id: courseId, + email: 'user22@example.com', + username: 'user22', + user_id: 22, + percent: 0, + letter_grade: null, + section_breakdown: [ + { + subsection_name: 'Demo Course Overview', + score_earned: 0, + score_possible: 0, + percent: 0, + displayed_value: '0.00', + grade_description: '(0.00/0.00)', + }, + { + subsection_name: 'Example Week 1: Getting Started', + score_earned: 1, + score_possible: 1, + percent: 0, + displayed_value: '0.00', + grade_description: '(0.00/0.00)', + }, + ], + }], + }; + + it('dispatches success action after fetching grades', () => { + const expectedActions = [ + { type: STARTED_FETCHING_GRADES }, + { + type: GOT_GRADES, + grades: responseData.results.sort(sortAlphaAsc), + cohort: expectedCohort, + track: expectedTrack, + headings: [ + { + columnSortable: true, + key: 'username', + label: 'Username', + onSort: expect.anything(), + }, + { + columnSortable: true, + key: 'total', + label: 'Total', + onSort: expect.anything(), + }, + ], + prev: responseData.previous, + next: responseData.next, + }, + { type: FINISHED_FETCHING_GRADES }, + { type: UPDATE_BANNER, showSuccess: false }, + ]; + const store = mockStore(); + + axiosMock.onGet(fetchGradesURL) + .replyOnce(200, JSON.stringify(responseData)); + + return store.dispatch(fetchGrades(courseId, expectedCohort, expectedTrack, false)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + + it('dispatches failure action after fetching grades', () => { + const expectedActions = [ + { type: STARTED_FETCHING_GRADES }, + { type: ERROR_FETCHING_GRADES }, + ]; + const store = mockStore(); + + axiosMock.onGet(fetchGradesURL) + .replyOnce(500, JSON.stringify({})); + + return store.dispatch(fetchGrades(courseId, expectedCohort, expectedTrack, false)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + }); +}); diff --git a/src/data/actions/tracks.js b/src/data/actions/tracks.js index 9b3bc28..81f43cc 100644 --- a/src/data/actions/tracks.js +++ b/src/data/actions/tracks.js @@ -17,7 +17,7 @@ const fetchTracks = courseId => ( .then((data) => { dispatch(gotTracks(data.course_modes)); }) - .catch((error) => { + .catch(() => { dispatch(errorFetchingTracks()); }); } diff --git a/src/data/actions/tracks.test.js b/src/data/actions/tracks.test.js new file mode 100644 index 0000000..891b07e --- /dev/null +++ b/src/data/actions/tracks.test.js @@ -0,0 +1,80 @@ +import configureMockStore from 'redux-mock-store'; +import MockAdapter from 'axios-mock-adapter'; +import thunk from 'redux-thunk'; + +import apiClient from '../apiClient'; +import { configuration } from '../../config'; +import { fetchTracks } from './tracks'; +import { + STARTED_FETCHING_TRACKS, + GOT_TRACKS, + ERROR_FETCHING_TRACKS, +} from '../constants/actionTypes/tracks'; + +const mockStore = configureMockStore([thunk]); +const axiosMock = new MockAdapter(apiClient); + +describe('actions', () => { + afterEach(() => { + axiosMock.reset(); + }); + + describe('fetchTracks', () => { + const courseId = 'course-v1:edX+DemoX+Demo_Course'; + + it('dispatches success action after fetching tracks', () => { + const responseData = { + course_modes: [ + { + slug: 'audit', + name: 'Audit', + min_price: 0, + suggested_prices: '', + currency: 'usd', + expiration_datetime: null, + description: null, + sku: '68EFFFF', + bulk_sku: null, + }, + { + slug: 'verified', + name: 'Verified Certificate', + min_price: 100, + suggested_prices: '', + currency: 'usd', + expiration_datetime: '2021-05-04T18:08:12.644361Z', + description: null, + sku: '8CF08E5', + bulk_sku: 'A5B6DBE', + }], + }; + const expectedActions = [ + { type: STARTED_FETCHING_TRACKS }, + { type: GOT_TRACKS, tracks: responseData.course_modes }, + ]; + const store = mockStore(); + + axiosMock.onGet(`${configuration.LMS_BASE_URL}/api/enrollment/v1/course/${courseId}`) + .replyOnce(200, JSON.stringify(responseData)); + + return store.dispatch(fetchTracks(courseId)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + + it('dispatches failure action after fetching tracks', () => { + const expectedActions = [ + { type: STARTED_FETCHING_TRACKS }, + { type: ERROR_FETCHING_TRACKS }, + ]; + const store = mockStore(); + + axiosMock.onGet(`${configuration.LMS_BASE_URL}/api/enrollment/v1/course/${courseId}`) + .replyOnce(500, JSON.stringify({})); + + return store.dispatch(fetchTracks(courseId)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + }); +}); diff --git a/src/data/reducers/cohorts.js b/src/data/reducers/cohorts.js index f53e8cd..ebc09d8 100644 --- a/src/data/reducers/cohorts.js +++ b/src/data/reducers/cohorts.js @@ -17,6 +17,7 @@ const cohorts = (state = initialState, action) => { return { ...state, results: action.cohorts, + finishedFetching: true, errorFetching: false, }; case STARTED_FETCHING_COHORTS: diff --git a/src/data/reducers/cohorts.test.js b/src/data/reducers/cohorts.test.js new file mode 100644 index 0000000..201a6e8 --- /dev/null +++ b/src/data/reducers/cohorts.test.js @@ -0,0 +1,70 @@ +import cohorts from './cohorts'; +import { + STARTED_FETCHING_COHORTS, + ERROR_FETCHING_COHORTS, + GOT_COHORTS, +} from '../constants/actionTypes/cohorts'; + +const initialState = { + results: [], + startedFetching: false, + errorFetching: false, +}; + +const cohortsData = [ + { + assignment_type: 'manual', + group_id: null, + id: 1, + name: 'default_group', + user_count: 2, + user_partition_id: null, + }, + { + assignment_type: 'auto', + group_id: null, + id: 2, + name: 'auto_group', + user_count: 5, + user_partition_id: null, + }]; + +describe('dashboardAnalytics reducer', () => { + it('has initial state', () => { + expect(cohorts(undefined, {})).toEqual(initialState); + }); + + it('updates fetch cohorts request state', () => { + const expected = { + ...initialState, + startedFetching: true, + }; + expect(cohorts(undefined, { + type: STARTED_FETCHING_COHORTS, + })).toEqual(expected); + }); + + it('updates fetch cohorts success state', () => { + const expected = { + ...initialState, + results: cohortsData, + errorFetching: false, + finishedFetching: true, + }; + expect(cohorts(undefined, { + type: GOT_COHORTS, + cohorts: cohortsData, + })).toEqual(expected); + }); + + it('updates fetch cohorts failure state', () => { + const expected = { + ...initialState, + errorFetching: true, + finishedFetching: true, + }; + expect(cohorts(undefined, { + type: ERROR_FETCHING_COHORTS, + })).toEqual(expected); + }); +});