Last active
February 7, 2019 03:16
-
-
Save dschinkel/99107b0808e41e66d2c7e9655d6e812c to your computer and use it in GitHub Desktop.
Route Component TDD Example - Retrospective
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from "react"; | |
const WithEditProjectQuestions: React.SFC = ({}) => { | |
return <div />; | |
}; | |
export default WithEditProjectQuestions; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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