Недавно я начал использовать библиотеку моментальных снимков Jest, чтобы ознакомиться с интерфейсным тестированием. Проблема, с которой я столкнулся, - это сообщения об ошибках. Я получаю сообщения об ошибках, несмотря на то, что приложение работает правильно.
Например:
В моем приложении есть панель навигации под названием <NavBar/>
, которая, как и большинство панелей навигации, содержит ссылки. Я использовал react-router-dom
для создания ссылок.
Вот скриншот компонента с инструментами разработки:
вот код
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;
Вот мой тест снимков для этого
/**
* @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();
});
Вот зарегистрированные сообщения об ошибках:
Чтение сообщений об ошибках привело меня к node_module react-router-dom/index.tsx:360:7
, и я нашел эту функцию, которая, как я понимаю, отвечает за создание ссылок в компоненте RRD
Что для меня не имеет смысла, так это то, что функция useHref()
ЕСТЬ в компоненте <Router>
, не напрямую, а если вы посмотрите на дерево DOM на снимке экрана React devtools, NavBar является дочерним компонентом других компонентов React, которые являются прямыми дочерними элементами < Роутер>
Вот упрощенный фрагмент моего компонента сверху вниз, чтобы продемонстрировать это дальше
/*////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;
В итоге, есть ли что-то функционально неправильное в моем коде (кажется, что это не так, ссылки работают и они успешно отображают все компоненты пользовательского интерфейса)? Или сообщение больше относится к лучшим практикам? Я читал в статье, что тестирование моментальных снимков не имеет ничего общего с проверкой правильности поведения компонента.
ссылка: https://www.sitepen.com/blog/snapshot-testing-benefits-and-drawbacks
Есть ли что-то в тестировании моментальных снимков, чего я не понимаю? Любые предложения о том, как двигаться дальше?
Вот также мой package.json и мой babelrc на случай, если это может помочь, похоже, проблем с зависимостями нет.
{
"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'} ]
]
}
Должно быть совершенно очевидно, что в модульном тесте компонент NavBar
не отображается в контексте маршрутизации.
it('renders <NavBar> correctly', () => {
const tree = renderer
.create(<NavBar />) // <-- just the NavBar, no router
.toJSON();
expect(tree).toMatchSnapshot();
});
Помните, что в модульных тестах вы тестируете отдельные блоки кода, а не все приложение. В приведенном выше тесте вы тестируете компонент NavBar
отдельно от всего остального кода. Это не означает, что они по-прежнему работают вне любых контекстов React, к которым они обращаются, т. е. избыточных хранилищ, контекстов маршрутизации/навигации и т. д.
Оберните компонент NavBar
в маршрутизатор так, чтобы ему был доступен контекст маршрутизации. MemoryRouter часто используется для модульного тестирования.
Пример:
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();
});
Последний вопрос. Я вижу, что тест проходит и с контекстом <BrowserRouter>. Значит ли это, что вы можете гипотетически использовать любой контекст маршрутизатора, который не зависит от другого? В документах указано использовать <MemoryRouter> специально, но есть ли какая-то причина для этого?
@ ls170292 MemoryRouter
часто используется при модульном тестировании в средах Node, поскольку он не зависит от какой-либо структуры DOM, то есть JSDOM.
Ах я вижу! Я не понял, что он тестировал его изолированно, а не в контексте дерева DOM. Таким образом, он проверяет его, как если бы у него не было этих родительских компонентов с контекстом маршрутизации. Поэтому вам нужно создать отдельный контекст маршрутизации для каждого теста. Спасибо, что прояснили это!