I've recently started using the Jest snapshot library in order to get familiar with front end testing. An issue I've come across are its error messages. I get error messages despite the app seemingly functioning correctly.
For example:
My app has a navigation bar called <NavBar/>
, as with most navigation bars it contains links. I used react-router-dom
to create the links.
Here's a screenshot of the component with dev tools:
here's the code
import React from 'react';
import { Link } from 'react-router-dom';
const NavBar = ({ userAuth, getUserAuth, authorized }) => {
return !authorized ? (
<div id='nav-bar'>
<div id='logo'><img src='https://res.cloudinary.com/darp0mj9i/image/upload/v1668059703/icons/Screen_Shot_2022-11-01_at_15.45.36_kzwlv1_sjvqta.svg'/></div>
<div id='nav-links'>
<p style={{marginRight: '10px'}}>Guest</p>
<Link to='/' id='link' className='link-login'>
<p>Home</p>
</Link>
<Link to='/login' id='link' className='link-login'>
<p>Login</p>
</Link>
</div>
</div>
) :
(
<div id='nav-bar'>
<div id='logo'><img src='https://res.cloudinary.com/darp0mj9i/image/upload/v1668059703/icons/Screen_Shot_2022-11-01_at_15.45.36_kzwlv1_sjvqta.svg'/></div>
<div id='nav-links'>
<p style={{marginRight: '10px'}} >{`Welcome ${userAuth.username} !`}</p>
<Link to='/' id='link' className='link-login'>
<p>Home</p>
</Link>
<Link to='/login' id='link' className='link-login'>
<p>Login</p>
</Link>
</div>
</div>
)
};
export default NavBar;
Here's my snapshot test for this
/**
* @jest-environment jsdom
*/
import renderer from 'react-test-renderer';
import { App, Login, Footer, NavBar } from '../components/testExports.js';
it('renders <NavBar> correctly', () => {
const tree = renderer
.create(<NavBar/>)
.toJSON();
expect(tree).toMatchSnapshot();
});
Here are the logged error messages:
Reading the error messages took me to the node_module react-router-dom/index.tsx:360:7
and I found this function which to my understanding looks like is responsible for creating links in the RRD component
What doesn't make sense to me is that the useHref()
function IS in a <Router>
component, not directly but if you look at the DOM tree in the react devtools screenshot NavBar is a child component of other React components that are direct children of the <Router>
Here's a simplified snippet of my component top-bottom to demonstrate this further
/*////index.js////*/
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './components/App.js';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App/>
</BrowserRouter>
);
/*////App.js////*/
import React, { useState, useEffect } from 'react';
import Router from './Router.js';
const axios = require('axios');
const App = () => {
return (
<div id='app'>
<Router userAuth={userAuth} getUserAuth={getUserAuth} userLogin={userLogin} authorized={authorized} userRegister={userRegister} />
</div>
);
}
export default App;
/*////Router.js////*/
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import Home from './Home.js';
import Login from './pages/Login.js'
const Router = ({ userAuth, getUserAuth, userLogin, authorized, userRegister }) => {
return (
<Routes>
<Route path='/' element={ <Home userAuth={userAuth} getUserAuth={getUserAuth} authorized={authorized} /> }/>
<Route path='/login' element={ <Login userAuth={userAuth} getUserAuth={getUserAuth} userLogin={userLogin} authorized={authorized} userRegister={userRegister} /> }/>
</Routes>
);
};
export default Router;
/*////Home.js////*/
// Global Modules
import React, { useState, useEffect } from 'react';
// Containers
import { Globe, Info, MakeYourOwn, CustomPlanet, CustomInfo, CustomGlobe, NavBar, Footer } from './Imports.js';
const axios = require('axios');
const Home = ({ userAuth, getUserAuth, authorized }) => {
// Main function that renders the planet onto the page
const newPlanet = () => {
if (state.build) { // if we're done building our custom planet
return (
<div id='home'>
<NavBar userAuth={userAuth} getUserAuth={getUserAuth} authorized={authorized} />
<div id='home-body'>
<Info
func={alertFunc}
data={state.data}
makeyourown={makeyourownClick}/>
<Globe planetName={state.planetName} />
</div>
<Footer/>
</div>
);
}
}
return (
<div id='container'>
{newPlanet()}
</div>
);
}
export default Home;
Bottom line, is there something functionally wrong with my code (there doesn't appear to be, the links work and they render all the UI component successfully)? Or is the message more of a best practices thing? I've read in an article that snapshot testing has nothing to do with testing whether the behavior of a component is correct.
ref: https://www.sitepen.com/blog/snapshot-testing-benefits-and-drawbacks
Is there something about snapshot testing that I'm not understanding? Any suggestions on how to move forward?
Here's also my package.json and my babelrc in case that might help, there don't seem to be any dependency issues
{
"name": "newapp",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"build": "npx webpack",
"test": "jest",
"server": "nodemon server/server.js",
"client": "npx webpack --watch"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@babel/core": "^7.18.10",
"@babel/preset-env": "^7.18.10",
"@babel/preset-react": "^7.18.6",
"@react-three/drei": "^9.20.0",
"@react-three/fiber": "^8.3.1",
"axios": "^0.27.2",
"babel": "^6.23.0",
"babel-jest": "^29.3.1",
"babel-loader": "^8.2.5",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"jest": "^29.3.1",
"nodemon": "^2.0.19",
"path": "^0.12.7",
"pg": "^8.7.3",
"postgres": "^3.2.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.4.0",
"react-router-dom": "^6.4.3",
"react-test-renderer": "^18.2.0",
"styled-components": "^5.3.5",
"three": "^0.143.0",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
},
"devDependencies": {
"file-loader": "^6.2.0",
"jest-environment-jsdom": "^29.3.1"
}
}
{
"presets": [
"@babel/preset-env",
[ "@babel/preset-react", {runtime: 'automatic'} ]
]
}
It should be fairly obvious that in the unit test the NavBar
component is not rendered within a routing context.
it('renders <NavBar> correctly', () => {
const tree = renderer
.create(<NavBar />) // <-- just the NavBar, no router
.toJSON();
expect(tree).toMatchSnapshot();
});
Remember that in unit tests you are testing single units of your code, not the entire application. In the above test you are testing the NavBar
component in isolation from all other code. This doesn't mean that they still work outside any React contexts they are accessing, i.e. redux stores, routing/navigation contexts, etc.
Wrap the NavBar
component in a router such that it has a routing context available to it. The MemoryRouter
is often used for unit testing.
Example:
import renderer from 'react-test-renderer';
import { MemoryRouter } from 'react-router-dom';
import { App, Login, Footer, NavBar } from '../components/testExports.js';
it('renders <NavBar> correctly', () => {
const tree = renderer
.create(
<MemoryRouter>
<NavBar />
</MemoryRouter>
)
.toJSON();
expect(tree).toMatchSnapshot();
});