Testing Redux
parent
9416e917f8
commit
f9ff64efd4
|
@ -5,7 +5,7 @@ import AboutPage from "./about/AboutPage";
|
||||||
import Header from "./common/Header";
|
import Header from "./common/Header";
|
||||||
import PageNotFound from "./PageNotFound";
|
import PageNotFound from "./PageNotFound";
|
||||||
import CoursesPage from "./courses/CoursesPage";
|
import CoursesPage from "./courses/CoursesPage";
|
||||||
import ManageCoursePage from "./courses/ManageCoursePage";
|
import ManageCoursePage from "./courses/ManageCoursePage"; // eslint-disable-line import/no-named-as-default
|
||||||
import { ToastContainer } from "react-toastify";
|
import { ToastContainer } from "react-toastify";
|
||||||
import "react-toastify/dist/ReactToastify.css";
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,13 @@ import { newCourse } from "../../../tools/mockData";
|
||||||
import Spinner from "../common/Spinner";
|
import Spinner from "../common/Spinner";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
function ManageCoursePage({ courses, authors, actions, history, ...props }) {
|
export function ManageCoursePage({
|
||||||
|
courses,
|
||||||
|
authors,
|
||||||
|
actions,
|
||||||
|
history,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
const [course, setCourse] = useState({ ...props.course });
|
const [course, setCourse] = useState({ ...props.course });
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from "react";
|
||||||
|
import { mount } from "enzyme";
|
||||||
|
import { authors, newCourse, courses } from "../../../tools/mockData";
|
||||||
|
import { ManageCoursePage } from "./ManageCoursePage";
|
||||||
|
|
||||||
|
function render(args) {
|
||||||
|
const defaultProps = {
|
||||||
|
authors,
|
||||||
|
courses,
|
||||||
|
// Passed from React Router in real app, so just stubbing in for test.
|
||||||
|
// Could also choose to use MemoryRouter as shown in Header.test.js,
|
||||||
|
// or even wrap with React Router, depending on whether I
|
||||||
|
// need to test React Router related behavior.
|
||||||
|
history: {},
|
||||||
|
actions: {
|
||||||
|
saveCourse: jest.fn(),
|
||||||
|
loadAuthors: jest.fn(),
|
||||||
|
loadCourses: jest.fn()
|
||||||
|
},
|
||||||
|
course: newCourse,
|
||||||
|
match: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = { ...defaultProps, ...args };
|
||||||
|
|
||||||
|
return mount(<ManageCoursePage {...props} />);
|
||||||
|
}
|
||||||
|
|
||||||
|
it("sets error when attempting to save an empty title field", () => {
|
||||||
|
const wrapper = render();
|
||||||
|
wrapper.find("form").simulate("submit");
|
||||||
|
const error = wrapper.find(".alert").first();
|
||||||
|
expect(error.text()).toBe("Title is required.");
|
||||||
|
});
|
|
@ -0,0 +1,52 @@
|
||||||
|
import * as courseActions from "./courseActions";
|
||||||
|
import * as types from "./actionTypes";
|
||||||
|
import { courses } from "../../../tools/mockData";
|
||||||
|
import thunk from "redux-thunk";
|
||||||
|
import fetchMock from "fetch-mock";
|
||||||
|
import configureMockStore from "redux-mock-store";
|
||||||
|
|
||||||
|
// Test an async action
|
||||||
|
const middleware = [thunk];
|
||||||
|
const mockStore = configureMockStore(middleware);
|
||||||
|
|
||||||
|
describe("Async Actions", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
fetchMock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Load Courses Thunk", () => {
|
||||||
|
it("should create BEGIN_API_CALL and LOAD_COURSES_SUCCESS when loading courses", () => {
|
||||||
|
fetchMock.mock("*", {
|
||||||
|
body: courses,
|
||||||
|
headers: { "content-type": "application/json" }
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: types.BEGIN_API_CALL },
|
||||||
|
{ type: types.LOAD_COURSES_SUCCESS, courses }
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = mockStore({ courses: [] });
|
||||||
|
return store.dispatch(courseActions.loadCourses()).then(() => {
|
||||||
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("createCourseSuccess", () => {
|
||||||
|
it("should create a CREATE_COURSE_SUCCESS action", () => {
|
||||||
|
//arrange
|
||||||
|
const course = courses[0];
|
||||||
|
const expectedAction = {
|
||||||
|
type: types.CREATE_COURSE_SUCCESS,
|
||||||
|
course
|
||||||
|
};
|
||||||
|
|
||||||
|
//act
|
||||||
|
const action = courseActions.createCourseSuccess(course);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
expect(action).toEqual(expectedAction);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,51 @@
|
||||||
|
import courseReducer from "./courseReducer";
|
||||||
|
import * as actions from "../actions/courseActions";
|
||||||
|
|
||||||
|
it("should add course when passed CREATE_COURSE_SUCCESS", () => {
|
||||||
|
// arrange
|
||||||
|
const initialState = [
|
||||||
|
{
|
||||||
|
title: "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "B"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const newCourse = {
|
||||||
|
title: "C"
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = actions.createCourseSuccess(newCourse);
|
||||||
|
|
||||||
|
// act
|
||||||
|
const newState = courseReducer(initialState, action);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(newState.length).toEqual(3);
|
||||||
|
expect(newState[0].title).toEqual("A");
|
||||||
|
expect(newState[1].title).toEqual("B");
|
||||||
|
expect(newState[2].title).toEqual("C");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update course when passed UPDATE_COURSE_SUCCESS", () => {
|
||||||
|
// arrange
|
||||||
|
const initialState = [
|
||||||
|
{ id: 1, title: "A" },
|
||||||
|
{ id: 2, title: "B" },
|
||||||
|
{ id: 3, title: "C" }
|
||||||
|
];
|
||||||
|
|
||||||
|
const course = { id: 2, title: "New Title" };
|
||||||
|
const action = actions.updateCourseSuccess(course);
|
||||||
|
|
||||||
|
// act
|
||||||
|
const newState = courseReducer(initialState, action);
|
||||||
|
const updatedCourse = newState.find((a) => a.id == course.id);
|
||||||
|
const untouchedCourse = newState.find((a) => a.id == 1);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(updatedCourse.title).toEqual("New Title");
|
||||||
|
expect(untouchedCourse.title).toEqual("A");
|
||||||
|
expect(newState.length).toEqual(3);
|
||||||
|
});
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { createStore } from "redux";
|
||||||
|
import rootReducer from "./reducers";
|
||||||
|
import initialState from "./reducers/initialState";
|
||||||
|
import * as courseActions from "./actions/courseActions";
|
||||||
|
|
||||||
|
it("Should handle creating courses", function () {
|
||||||
|
// arrange
|
||||||
|
const store = createStore(rootReducer, initialState);
|
||||||
|
const course = {
|
||||||
|
title: "Clean Code"
|
||||||
|
};
|
||||||
|
|
||||||
|
// act
|
||||||
|
const action = courseActions.createCourseSuccess(course);
|
||||||
|
store.dispatch(action);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const createdCourse = store.getState().courses[0];
|
||||||
|
expect(createdCourse).toEqual(course);
|
||||||
|
});
|
Loading…
Reference in New Issue