Note: Slides do not tell the whole story of the talk, so take the stand alone slides with a grain of salt. Things may be taken out of context.
Note: Slides do not tell the whole story of the talk, so take the stand alone slides with a grain of salt. Things may be taken out of context.
Having issues testing Formik with react-testing-library? Validation errors not showing up? Formik validation happens asynchronously, so you need to use react-testing-library’s findBy* methods and await for the validation to finish (which can be done by waiting for some UI to show up). Scroll to the very bottom to see a working test.
Formik is a library for building forms in React. It abstracts away some of the annoying part of forms like validation, errors, fields, etc. so that you don’t have to. It can also do super useful things like keep track of if a field has been touched by the user or not yet, so you can delay showing validation errors until they actually interact with the field.
react-testing-library is… well… exactly what it sounds like – a library for testing React. The library forces you to write tests that follow it’s primary principle:
The more your tests resemble the way your software is used, the more confidence they can give you.
In my opinion, this is one of the single most important aspects of writing good tests. Tests that do too much mocking or too much “make sure this function is called with this data” is usually not a very useful test in my eyes.
Besides, giving you confidence the software is working – one of the biggest values of automated tests is giving you the confidence to to be able to refactor the internals of your code later on. However, if the test knows too much about the internals, then you usually have to refactor both the code and the test, which can be really problematic, in my experience.
Ok – soapbox over.
Let’s say I have an InputField component. This component will have 3 goals to accomplish:
That way all of our labels, inputs, and validation errors are consistent across our entire application.
| import React from "react"; | |
| import { Field } from "formik"; | |
| const InputField = (props) => { | |
| return ( | |
| <Field name={props.fieldName}> | |
| {({ field, form }) => ( | |
| <div> | |
| <label htmlFor={props.fieldName}>{props.labelName}</label> | |
| <input {…field} id={props.fieldName} type="text" /> | |
| {form.errors[props.fieldName] && form.touched[props.fieldName] ? ( | |
| <div data-testid={`errors-${props.fieldName}`}> | |
| {form.errors[props.fieldName]} | |
| </div> | |
| ) : null} | |
| </div> | |
| )} | |
| </Field> | |
| ); | |
| }; | |
| export default InputField; |
Note: I’m omitting a few things you may want in real life (like PropTypes, placeholders, making the input type a prop, styling validation errors, etc.) for brevity since it’s not super important for this blog post.
Alright – let’s go to test the InputField component (normally we’d write the test first if we were doing TDD, so this is a little backwards, but bear with me).
One of the tests we need to write is to make sure the validation errors show up correctly. The flow of that test will be like this:
In order to achieve this, you may start out with a test like this:
| import React from "react"; | |
| import { screen, render, fireEvent } from "@testing-library/react"; | |
| import InputField from "./InputField"; | |
| import { Formik } from "formik"; | |
| test("should have validation error given input field is touched and error exists on form", () => { | |
| const fieldName = "firstName"; | |
| const labelName = "First Name"; | |
| render( | |
| <Formik | |
| validate={(values) => { | |
| let errors = {}; | |
| if (!values?.firstName) { | |
| errors.firstName = "Required."; | |
| } | |
| return errors; | |
| }} | |
| > | |
| <InputField fieldName={fieldName} labelName={labelName} /> | |
| </Formik> | |
| ); | |
| const input = screen.getByLabelText(labelName); | |
| // Call blur without inputting anything which should trigger a validation error | |
| fireEvent.blur(input); | |
| const validationErrors = screen.getByTestId(`errors-${fieldName}`); | |
| expect(validationErrors.innerHTML).toBe("Required."); | |
| }); |
However this test will fail, because it says that the div with our validation errors is not rendered (notice, it should be below the input tag, but it’s not):

So what’s the problem? Well – Formik validation happens asynchronously, so while on line 28 above we call blur without entering any data in order to trigger a validation error. On line 30 we immediately go looking for that validation error. The problem is – Formik validation hasn’t finished executing when we go look for the UI to change, so when we go to expect our validation error UI is there, it’s not because the validation is still executing.
So the solution then is to make the call to find the UI asynchronously via findByTestId instead of getByTestId. All of the findBy* functions in react-testing-library are asynchronous and react-testing-library will wait up to 4.5 seconds for the UI to appear before failing the test, which should give Formik enough time to run validation in this case.
So the successful test can look something like this:
| import React from "react"; | |
| import { screen, render, fireEvent } from "@testing-library/react"; | |
| import InputField from "./InputField"; | |
| import { Formik } from "formik"; | |
| test("should have validation error given input field is touched and error exists on form", async () => { | |
| const fieldName = "firstName"; | |
| const labelName = "First Name"; | |
| render( | |
| <Formik | |
| validate={(values) => { | |
| let errors = {}; | |
| if (!values?.firstName) { | |
| errors.firstName = "Required."; | |
| } | |
| return errors; | |
| }} | |
| > | |
| <InputField fieldName={fieldName} labelName={labelName} /> | |
| </Formik> | |
| ); | |
| const input = screen.getByLabelText(labelName); | |
| // Call blur without inputting anything which should trigger a validation error | |
| fireEvent.blur(input); | |
| const validationErrors = await screen.findByTestId(`errors-${fieldName}`); | |
| expect(validationErrors.innerHTML).toBe("Required."); | |
| }); |
I changed line 6 to make the test async and line 30 to call await screen.findByTestId instead. Now the test passes:

Hope this helps! An example repo can be found here.
Note: Slides do not tell the whole story of the talk, so take the stand alone slides with a grain of salt. Things may be taken out of context.
Code: Blazor ToDoMVC and Blazor on Electron
Note: Slides do not tell the whole story of the talk, so take the stand alone slides with a grain of salt. Things may be taken out of context.
Code: Blazor ToDoMVC and Blazor on Electron