By Khalil Bouaazzi on 3/23/2021
Testing: The final frontier (or in TDD, the initial frontier). There are a ton of ways to do it but in general, it’s hard to know what to test. With all the different testing frameworks out there, it’s also pretty hard to choose how to test. At OrderMyGear, when it comes to unit testing, we wanted the ability to test using something that would work for our internal UI framework library as well as our micro-ui server-side rendered apps.
Here at OMG, our SSR apps are a blend of Node/Express backends with React frontends written in Typescript. We also have a dedicated component library that needs to be tested and we wanted our selection to work with all the above. We settled with Jest being our test runner as it is customizable, easy to use, and very well documented. As far as the frontend, our big decision was to choose between React Testing Library and Enzyme.
This post is the first in a multi part series where I talk about using React Testing Library, Emotion, and Typescript. This will talk about ways to classify components and test simple components. Future parts dive into more complex components with internal state, reducers, and asynchronous behavior.
Before jumping into testing components, I think it’s important to discuss what kinds of components need to be tested. What to test generally has to do with how complex a component is. A good way to analyze how complex a component might be would be to try to classify the component using Atomic design principles.
Atomic design is a concept that helps create a mental model of all the things that we need to do to develop a new user interface. It adds structure and builds a loose schedule of things that need to be done. This article isn’t necessarily about atomic design but thinking of a user interface in terms of atoms, molecules, organisms, templates and pages will help us when writing testing.
Atomic design is composed of five distinct categories:
While this article is not meant to be about atomic design, it is important to understand what kind of components require what levels of testing. Oftentimes, atoms and molecules have interactions that are defined by the organisms that contain them and thus, limits what is truly testable. This article is about how to write effective tests given the scope of a particular component.
With simple components, it is important to understand all of the states a component can be in and what interactions can trigger these states. To demonstrate this, consider an atom <Button />
component. A <Button />
can be hovered over and it can be clicked as well as it’s default, neutral state. When clicked, it should do something.
Using Jest and the React Testing Library, we can easily test a typical atom component. Note that atom and smaller molecule components can include a snapshot test. This sort of test is useful for components like those that will live in the ui-framework. As soon as they’re built and are adopted by other repos, they should require very few changes. If a change is necessary, we can delete the previous snapshot in lieu of an updated one. Below is a good example of a simple atom component:
|
In this case, we have a button styled using emotion.js. It accepts one handler function prop that is called when the button is clicked. Now let’s look at a few jest tests for this button, which we choose to place in the same directory as the component itself:
// Button.test.tsx (Note that tests are colocated with components) describe(“”<Button />””, () => {test(“”it should render a button””, async () => { const mockClick = jest.fn(); const { getByText } = render( <Button onClick={mockClick}>NiceButton</Button> ); const TestButton = getByText(“”NiceButton””); expect(TestButton).toBeInTheDocument(); expect(TestButton).toMatchSnapshot(); });
|
Our first test is a snapshot test: given a button, can we verify that it consistently renders in the same way every time? As mentioned previously, this kind of test is particularly useful for components within a shared library that doesn’t change often. The line expect(TestButton).toMatchSnapshot(); will generate a snapshot file that is then compared against all future runs of this test (including on your CI server). Our second test verifies that the behavior of the button is what we expect. Given a button, we pass down a jest mock function that can report back to the test how often it was called. You can see how the last line of our example verifies that it was called exactly one time if someone clicks on it.
Testing Molecules
Now that we have a <Button />
we can create a <ButtonGroup />
molecule component. As molecules begin to grow and change, they may not be as resilient to snapshot testing. This can be handled on a case by case basis but by default, we should include a snapshot everywhere we can. In this example, we can pass two props: a “children” prop that allows us to pass any number of Buttons, and a “direction” prop that allows us to specify a vertical or horizontal orientation.
|
Now that we have a functional ButtonGroup, let’s write some tests for it:
|
By now, you should have a decent grasp on how to test atom and molecule-sized components. Check out the next post for more advanced component testing!
Khalil Bouaazzi – OMG’er #114
Senior Software Engineer
Connect with me on LinkedIn
About OrderMyGear
OrderMyGear is an industry-leading sales tool, empowering dealers, distributors, decorators, and brands to create custom online pop-up stores to sell branded products and apparel. Since 2008, OMG has been on a mission to simplify the process of selling customized merchandise to groups and improve the ordering experience. With easy-to-use tools, comprehensive reporting, and unmatched support, the OMG platform powers online stores for over 3,000 clients generating more than $1 billion in online sales. Learn more at www.ordermygear.com.
Media Contact: Lauren Seip | lauren.seip@ordermygear.com | 281-756-7915