Skip to content

Instantly share code, notes, and snippets.

@dschinkel
Last active February 7, 2019 03:16
Show Gist options
  • Save dschinkel/99107b0808e41e66d2c7e9655d6e812c to your computer and use it in GitHub Desktop.
Save dschinkel/99107b0808e41e66d2c7e9655d6e812c to your computer and use it in GitHub Desktop.
Route Component TDD Example - Retrospective
// makes Part2 test pass & removed what we don't need
import * as React from "react";
import { RouteComponentProps } from "react-router";
import WithEditProjectQuestions from "containers/recruiter/WithEditProjectQuestions";
import ProjectQuestionsForm from "components/recruiter/ProjectQuestionsForm";
export class EditProjectQuestions extends React.Component {
public render() {
// we're passing it a dummy function that does nothing to satisfy the ProjectQuestionsForm contract just to get us by
// with the minimal possible so that we can at least compile
return <ProjectQuestionsForm onSubmit={questions => Promise.resolve()} data-testid={"edit-project-questions"} />;
}
}
const WithEditProjectQuestionsMutation: React.SFC<
RouteComponentProps<{ id: number }>
> = routeComponentProps => {
return (
<WithEditProjectQuestions>
<EditProjectQuestions />
</WithEditProjectQuestions>
);
};
export default WithEditProjectQuestionsMutation;
/*
so since we can't test the way we wanted at first, the only other test we could write in here since
there is barely any behavior is that we expect that it's wired it up to render the projects question form for this
route component's render.
*/
import * as React from "react";
import { shallow } from "enzyme";
import { EditProjectQuestions }from "../EditProjectQuestions";
describe("Update a Project with Questions", () => {
it("shows a questions form", () => {
const editprojectQuestionsRoute = shallow(
<EditProjectQuestions />);
const editProjectQuestions = editprojectQuestionsRoute.find(
'[data-testid="edit-project-questions"]',
);
expect(editProjectQuestions.length).toEqual(1);
});
});
//routes/recruiter/projects/EditProjectQuestions.tsx
/*
Jist of it is that I was not able to use shallow on this component.
Problems testing at this level based on our architecutre:
Shallow
- you can't dive() more than once or you'll get an error. You have to try to dive so that you can get past
the HOC WithEditProjectQuestions and all the HOCs it also renders (AlertContext, Mutation, etc.). Because I wasn't
able to double shallow because it says whatever it's returning on first dive isn't a component I'm pretty much
screwed and must resort to using mount()
Mount
- problem with mount is many and I hate integration tests for exactly the issues I came across and reasons thereof that
come with the nature of integration tests as well as the fact that integration tests don't tell you where the bug is,
they take longer to code, they're slow, and their fragile. These are the reasons you don't write integration tests when
you TDD or at least I don't use mount and use shallow and call them component tests and it's why integration tests
are a waste of time IMO.
So here in this code in addition to the above symptoms of integration tests, I came across these common issues:
- First you're creating an integration test which forces rendres of all components including
HOCs and ultimately children to render
- Because of that, you have to satisfy each HOC's contract by sending in proper props
that each expect or else you won't even be able to compile. This makes for messy tests and time consuming creation of
tests besides having fragile tests due to mount
Conclusion: There is not a lot going on but it would have been nice to start TDD'ing at this level and the only
test we'd need is to test that we're wiring up the expected child in the With HOC here.
*/
import * as React from "react";
import { RouteComponentProps } from "react-router";
import WithEditProjectQuestions from "containers/recruiter/WithEditProjectQuestions";
import ProjectQuestionsForm from "components/recruiter/ProjectQuestionsForm";
const questions = [
{
id: 1,
order: 1,
text: "Why do you think you would be a good fit for this project?",
},
{
id: 2,
order: 2,
text: "What is your favorite color?",
},
{
id: 3,
order: 3,
text: "Do you like dogs or cats?",
},
];
class EditProjectQuestions extends React.Component {
public render() {
return <ProjectQuestionsForm onSubmit={questions => Promise.resolve()} />;
}
}
const WithEditProjectQuestionsMutation: React.SFC<
RouteComponentProps<{ id: number }>
> = routeComponentProps => {
return (
<WithEditProjectQuestions>
<EditProjectQuestions data-testid={"edit-project-questions"} />
</WithEditProjectQuestions>
);
};
export default WithEditProjectQuestionsMutation;
import * as React from "react";
import { shallow } from "enzyme";
import EditProjectQuestions from "../EditProjectQuestions";
describe("Update a Project with Questions", () => {
it("shows a questions form", () => {
// I also don't like using Jest Mock, it's unecessary
// we should be able to send in a simple anonymous function dummy!
// you can simply use const mock: any = () => {};
const mock: any = jest.fn();
const editprojectQuestionsRoute = shallow(
<EditProjectQuestions match={mock} history={mock} location={mock} />
).dive();
const editProjectQuestions = editprojectQuestionsRoute.find(
'[data-testid="edit-project-questions"]',
);
expect(editProjectQuestions.length).toEqual(1);
});
});
import * as React from "react";
const WithEditProjectQuestions: React.SFC = ({}) => {
return <div />;
};
export default WithEditProjectQuestions;
/*
note: all files in here actually have an extension of .tsx but I renamed to end in .js for syntax highlighting.
note: the below says it's for Create Project because I dupped that file to create this one. But imagine the below just having
similar code in it eventually for project questions, you come across the same issues that I stated earlier with the first
test
*/
import { GraphQLError } from "graphql";
import gql from "graphql-tag";
import * as React from "react";
import { Mutation } from "react-apollo";
import { State } from "helpers/US-states";
import { Project, Company, Question, Skill, LevelOfEducation } from "types";
import { AlertType } from "types";
import { AlertContext } from "containers/shared/AlertProvider";
export const CREATE_PROJECT = gql`
mutation createProject($projectInput: ProjectInput!) {
createProject(projectInput: $projectInput) {
id
name
department
description
startDate
dueDate
estimatedHours
paymentAmount
isOnSite
city
state
numberOfConsultants
levelOfEducation
company {
id
}
}
}
`;
export type ICreateProjectProps = {
city: string;
state: State | string;
name: string;
company: Company;
questions?: Question[];
skills?: Skill[];
startDate?: Date;
department?: string;
dueDate?: Date;
isOnSite: boolean;
estimatedHours?: number;
paymentAmount?: number;
description?: string;
levelOfEducation?: LevelOfEducation;
numberOfConsultants?: number;
};
export interface IWithEditProjectQuestionsChildProps {
error?: GraphQLError;
loading: boolean;
data: any;
onCreateProject(project: Partial<Project>): Promise<void>;
}
export interface IWithEditProjectQuestionsProps {
children(props: IWithEditProjectQuestionsChildProps): JSX.Element;
}
export const onCreateProject = (createProject: any, addAlert: any) => (
project: Partial<Project>,
) => {
return createProject({
variables: {
projectInput: {
city: project.city,
department: project.department,
description: project.description,
dueDate: project.dueDate,
estimatedHours: project.estimatedHours,
isOnSite: project.isOnSite,
levelOfEducation: project.levelOfEducation,
name: project.name,
numberOfConsultants: project.numberOfConsultants,
paymentAmount: project.paymentAmount,
skills: project.skills,
startDate: project.startDate,
state: project.state,
questions: project.questions
},
},
}).catch((err: GraphQLError) => {
addAlert({ type: AlertType.Error, message: err.message });
return Promise.reject(err);
});
};
const WithCreateProject: React.SFC<IWithCreateProjectProps> = ({
children,
}) => {
return (
<AlertContext.Consumer>
{({ addAlert }) => (
<Mutation mutation={CREATE_PROJECT}>
{(createProject, { loading, error, data }) => {
return children({
onCreateProject: onCreateProject(createProject, addAlert),
loading,
error,
data,
});
}}
</Mutation>
)}
</AlertContext.Consumer>
);
};
export default WithCreateProject;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment