Testing flow creating a React Web-page
?????? Nitsan Cohen
Frontend Engineer | Microfrontends Specialist | React & TypeScript Expert
Testing is a field in programming that is usually neglected, especially for beginners.
The main misconception is about the necessity of tests when creating websites/applications. The developer says to himself "Well I can SEE that it's working, so why I should test anything?"
Well, first of all, tests can help us recognize things that we don't see, but moreover and most important is - tests help us integrate new features to our websites without breaking the ones that already exist (or at least knowing that it is broken right on time and fix it immediately).
In this article, I will show you my own test flow when building a React webpage (using the create-react-app setup). We will create a NavBar component and show all the variety of tests we have to apply to it.
Please notice - I will not teach you how to set up the test environment. Also in this article, I will not teach you how to make tests. We will see some simple tests but this article is mainly about getting a feel for the different kinds of tests and tools and understanding why it is so important.
Manual testing, Visual testing, Snapshot testing - Storybook
Let's begin with my favorite tool for testing - Storybook. We'll perform three kinds of tests with it - Manual tests, Snapshot tests, and Visual tests.
According to the Storybooks' official website Storybook is:
Storybook?is an open-source tool for building UI components and pages in isolation. It streamlines UI development, testing, and documentation.
In simple words, Storybook lets us build components and see them live on our screen, without running them in a full application setup. It also has some testing tools and lets us document our components (if we are building a components library, for example).
The first test we are going to make is a manual test. As might already guess manual tests are all about seeing our component and validating that it looks as we expect it to be. As mentioned Storybook is about seeing our components so obviously it will suit the best for this kind of test.
Let's begin by creating our NavLink components:
import?*?as?React?from?'react';
import?PropTypes?from?'prop-types';
import?{?NavLink?as?ReactRouterNavLink?}?from?'react-router-dom';
import?{?StyledListItem?}?from?'./styles';
const?NavLink?=?({?children,?page,?styles,?activeStyles?})?=>?{
??const?[isHovering,?setIsHovering]?=?React.useState(false);
??return?(
????<StyledListItem
??????onMouseEnter={()?=>?setIsHovering(true)}
??????onMouseLeave={()?=>?setIsHovering(false)}
??????isHovering={isHovering}
????>
??????<ReactRouterNavLink?style={styles}?activeStyles={activeStyles}?to={page}>
????????{children}
??????</ReactRouterNavLink>
????</StyledListItem>
??);
};
NavLink.propTypes?=?{
??children:?PropTypes.string.isRequired,
??page:?PropTypes.string,
??styles:?PropTypes.object,
??activeStyles:?PropTypes.object,
};
export?default?NavLink;
It is a very simple NavLink component that receives all of its properties dynamically from the props object. Now in order to present it with all its styles, we have to create a parent component and pass all of its props. Or do we really have to?
Manual testing
With Storybook we don't have to! We create a story the describes all the props we want to pass to our component. A single component can have several stories that describe how it looks in different situations (different props). For this example, we will make only one kind of story. The story looks like this:
import?React?from?"react"
import?NavLink?from?"../components/NavLink/NavLink"
export?default?{
??component:?NavLink,
??title:?"NavBar/NavLink",
}
const?defaultStyles?=?{
??color:?"white",
??fontFamily:?"Spartan",
??fontStyle:?"normal",
??fontWeight:?"?bold",
??fontSize:?"11px",
??lineHeight:?"25px",
??letterSpacing:?"1px",
??textTransform:?"uppercase",
??textDecoration:?"none",
}
const?Template?=?args?=>?<NavLink?{...args}?/>
export?const?Default?=?Template.bind({})
Default.args?=?{
??styles:?{
????...defaultStyles,
??},
activeStyle: {
...defaultStyles,
??page:?"/Earth",
??children:?"Earth",
}
That's it! Now we can preview our component using Storybook. The really nice thing with Storybook is that you can change the props on the fly. Have a look at the next short video:
The thing about manual testing is that it's all done manually. This is very important but think about a component library with thousands of components. Checking each one of them manually after implementing a new feature is exhausting. This is where visual and snapshot tests come into place.
Snapshot testing in Storybook is really a piece of cake. You just have to activate a function from the addon-storyshots library and Storybook will take care of everything for you.
import?initStoryshots?from?'@storybook/addon-storyshots';
initStoryshots();
Behind the scenes, Storybook will use Jest to perform the snapshot tests. The really nice thing about it is that all of our stories for each component are automatically being snapshot tested so we don't have to write each test manually.
This is the result of the snapshot test for our component:
exports[`Storyshots NavBar/NavLink Default 1`] = `
<li
? className="sc-bdnxRM kqHtUy"
? onMouseEnter ={[Function]}
? onMouseLeave ={[Function]}
>
? <a
? ? href="/Earth"
? ? onClick ={[Function]}
? ? style={
? ? ? Object {
? ? ? ? "color": "white",
? ? ? ? "fontFamily": "Spartan",
? ? ? ? "fontSize": "11px",
? ? ? ? "fontStyle": "normal",
? ? ? ? "fontWeight": " bold",
? ? ? ? "letterSpacing": "1px",
? ? ? ? "lineHeight": "25px",
? ? ? ? "textDecoration": "none",
? ? ? ? "textTransform": "uppercase",
? ? ? }
? ? }
? >
? ? Earth
? </a>
</li>
`;
Now that we have our DOM element captured, every time we run the tests it will be compared with the new snapshot. If everything is the same it will pass, if not it will fail and inform us. A failing test does not necessarily mean we have a bug. Let's say we want to change our font size to 10px. This is the result after running the test again:
- Snapshot? - 1
? ? + Received? + 1
? ? @@ -8,11 +8,11 @@
? ? ? ? ? onClick ={[Function]}
? ? ? ? ? style={
? ? ? ? ? ? Object {
? ? ? ? ? ? ? "color": "white",
? ? ? ? ? ? ? "fontFamily": "Spartan",
? ? -? ? ? ? ?"fontSize": "11px",
? ? +? ? ? ? ?"fontSize": "10px",
? ? ? ? ? ? ? "fontStyle": "normal",
? ? ? ? ? ? ? "fontWeight": " bold",
? ? ? ? ? ? ? "letterSpacing": "1px",
? ? ? ? ? ? ? "lineHeight": "25px",
? ? ? ? ? ? ?
"textDecoration": "none",
Notice the double fontSize? It tells us that something has changed and the test failed. For us, it is fine because we know that we made this change on purpose.
领英推荐
Visual testing
Visual testing is not made by humans and is less error-prone than snapshots testing since it does not compare the DOM elements but rather an actual picture of the element.
In order to perform visual tests, we'll have to use another tool that integrates with Storybook which is called Chromatic. It has the purpose of deploying our Storybook library to an external website and it also helps us with continuous deployment.
Every time we commit and push our code to git Chromatic will compare the previous visual state of our component to the current one. That's really great!
Unit testing, Integration testing - Jest
Unit testing is one of the most important tests you can do when building your React app. You can think about it like building a building. If the core building blocks of your contraction material are in good shape, then probably your complete building will do well in the future.
I really like to do my unit testing with my stories from Storybook. This way I can ensure that the component is working well with all its props in various situations.
Let's have a look at a really simple unit test for our NavLink component:
import?React?from?'react';
import?{?BrowserRouter?as?Router?}?from?'react-router-dom';
import?{?Default?}?from?'../stories/NavLink.stories';
import?{?render,?screen?}?from?'@testing-library/react';
import?'@testing-library/jest-dom/extend-expect';
it('renders?the?text?of?the?button?component',?()?=>?{
??render(
????<Router>
??????<Default?{...Default.args}?/>
????</Router>
??);
??expect(screen.getByRole('navigation')).toHaveTextContent('Earth');
});
As you can see we import our "Default" story and some helper functions from the react testing library. We are then checking our component if it is rendered with the correct text.
Integration tests
For integration tests, we will use the Jest library again. Integration tests are all about how components work together. So in order to make such a test, we will have to create a new component - NavBar:
const?activeStyles?=?{?borderTop:?'solid?4px?#d83a34'?};
const?linksList?=?[
??'Mercury',
??'Venus',
??'Earth',
??'Mars',
??'Saturn',
??'Jupiter',
??'Neptune',
??'Uranus',
];
const?NavBar?=?()?=>?{
??return?(
????<StyledUnorderedList>
??????{linksList.map(link?=>?(
????????<NavLink
??????????children={link}
??????????key={nanoid()}
??????????activeStyles={activeStyles}
??????????styles={defaultStyles}
??????????page={`/${link}`}
????????/>
??????))}
????</StyledUnorderedList>
??);
};
export?default?NavBar;
We want to test if when we click on a NavLink it gets its activeStyle applied. The test will simulate a click on a NavLink and then will check if the style is preset. This is how it looks:
import?React?from?'react';
import?{?BrowserRouter?as?Router?}?from?'react-router-dom';
import?NavBar?from?'../../components/NavBar/NabBar';
import?{?render,?screen,?fireEvent?}?from?'@testing-library/react';
import?'@testing-library/jest-dom/extend-expect';
it('Gives?the?active?style?when?clicked',?()?=>?{
??render(
????<Router>
??????<NavBar?/>
????</Router>
??);
??const?navLink?=?screen.getByRole('/Earth');
??fireEvent.click(navLink);
??expect(navLink).toHaveStyle('border-top:?solid?4px?#d83a34');
});
This is really a simple test and for this use case, it is probably redundant. We are actually testing the functionality of NavLink from the React-Router-DOM library. We don't want to test functionalities of 3rd party libraries because it's their job to test it, not ours :)
Anyhow, for this example, this is sufficient.
End to end (E2E) testing - Cypress
Now it's time for the fun part. E2E testing means we are testing our full application as a whole. You would usually like to test the interaction of the user with a variety of elements in your DOM and the interaction of your application with external APIS.
The enjoyable part of Cypress tests is the tests actually run in a real environment - the browser. You specify the actions you want the test to perform in your browser and just sit back and enjoy.
Let's make a simple test where we press one of our NavLinks and check if it navigates us to the correct page:
import?React?from?'react';
import?{?mount?}?from?'@cypress/react';
import?App?from?'../App';
it('renders?learn?Earth?link?and?navigates?to?it',?()?=>?{
??mount(<App?/>);
??cy.get('ul?li:last').should('be.visible').click();
??cy.location('pathname').should('match',?/\/Uranus$/);
??cy.contains('Uranus').should('be.visible');
});
And let's see how it looks in the graphical view of Cypress (Watch closely it happens really quick!):
Summary
In this article, we saw 6 kinds of important tests:
Happy testing :)
I Help Tech companies transform their vision into paying products. Proven success with $100M+ Industry Leaders, Align your product with customers and investors in 90 days
1 个月???? ??? ?? ?? ???????? ??? ????? ???? ?????? ???: ?????? ????? ??? ??????? ?????? ??????, ?????? ?????? ??????,?????? ????? ????????. https://chat.whatsapp.com/BubG8iFDe2bHHWkNYiboeU
CEO and security engineer
2 个月???? ??? ?? ?? ?????? ??????? ??? ???? ???? ????? ???? ?????? ???: https://chat.whatsapp.com/HWWA9nLQYhW9DH97x227hJ