Тестирование вложенных компонентов React с использованием React-Test-Renderer - PullRequest
2 голосов
/ 09 марта 2020

У меня есть чистое приложение React-Redux, и оно работает как положено.

Приложение . js

import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
import { Router, Route, Switch, Redirect } from "react-router-dom";

import history from "../history";
import LandingPage from "./home/LandingPage";
import { displayModules } from "../actions";
import Cart from "./home/Cart";

const App = () => {
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(displayModules());
  }, [dispatch]);

  return (
    <Router history={history}>
      <Switch>
        <Route path="/" exact component={LandingPage}></Route>
        <Route path="/cart" exact component={Cart}></Route>
        <Route render={() => <Redirect to="/" />} />
      </Switch>
    </Router>
  );
};

export default App;

LandingPage имеет вложенный компонент с именем Tile.

import React from "react";
import { useSelector, useDispatch } from "react-redux";
import Tile from "../common/Tile";

import { addItemToCart, displayCartContents } from "../../actions";
import "./LandingPage.css";

const LandingPage = () => {
  const modules = useSelector(state => state.data.modules);
  const cart = useSelector(state => state.data.cart);
  const dispatch = useDispatch();
  const addToCart = item => {
    dispatch(addItemToCart(item));
  };
  return (
    <div className="app">
      <div className="header">
        <div className="text">Insurance modules</div>
        <i
          className="shopping cart icon"
          onClick={() => {
            dispatch(displayCartContents());
          }}
        >
          <span className="badge">{cart.length}</span>
        </i>
      </div>
      <div className="body">
        {modules.map(module => (
          <Tile key={module.id} module={module} addToCart={addToCart}></Tile>
        ))}
      </div>
    </div>
  );
};

export default LandingPage;

Tile. js имеет кнопку, которую я хочу проверить.

import React, { useState } from "react";

import "./Tile.css";

const Tile = props => {
  const { module, addToCart } = props;
  const [coverage, setCoverage] = useState(parseInt(module.coverageMax - module.coverageMin) / 2);
  const [price, setPrice] = useState((coverage * module.risk) / 100);
  return (
    <div className="tile">
      <div className="tile-description">
        <div>
          <i className={`${module.icon} icon`}></i>
        </div>
        <div className="tile-name">{module.name}</div>
        <div className="tile-risk">Risk(%): {module.risk}</div>
      </div>
      <div className="tile-footer">
        <div className="tile-range">
          <div className="field-label">
            Select Coverage: <span className="coverage-display">{coverage}</span>
          </div>
          <div className="slidecontainer">
            <span className="slider-step">{module.coverageMin}</span>
            <input
              type="range"
              min={module.coverageMin}
              max={module.coverageMax}
              value={coverage}
              className="slider"
              onChange={e => {
                setCoverage(e.target.value);
                setPrice((e.target.value * module.risk) / 100);
              }}
            ></input>
            <span className="slider-step">{module.coverageMax}</span>
          </div>
        </div>
        <div>
          PRICE at this Coverage:<span className="tile-price">{price}</span>
        </div>

        <button
          className="tile-button"
          onClick={() => {
            addToCart({
              id: module.id,
              name: module.name,
              coverage: coverage,
              price: price,
              timeStamp: Math.ceil(new Date().getTime() * Math.random() * Math.random())
            });
          }}
        >
          Add module to cart
        </button>
      </div>
    </div>
  );
};

export default Tile;

App.test. js работает нормально, и я могу найти вложенную страницу Landing div по className prop.

import React from "react";
import configureStore from "redux-mock-store";
import { Provider } from "react-redux";
import renderer from "react-test-renderer";

import App from "../components/App";
import history from "../history";
import { displayModules } from "../actions";
import { DISPLAY_MODULES } from "../actions/types";

const mockStore = configureStore([]);

describe("App Component test", () => {
  let store = {};
  let wrappedComponent = {};
  const expectedActions = {
    type: DISPLAY_MODULES,
    payload: [
      {
        id: 0,
        icon: "bicycle",
        name: "Bike",
        coverageMin: 0,
        coverageMax: 3000,
        risk: 30
      },
      {
        id: 1,
        icon: "gem",
        name: "Jewelry",
        coverageMin: 500,
        coverageMax: 10000,
        risk: 5
      },
      {
        id: 2,
        icon: "microchip",
        name: "Electronics",
        coverageMin: 500,
        coverageMax: 6000,
        risk: 35
      },
      {
        id: 3,
        icon: "football ball",
        name: "Sports Equipment",
        coverageMin: 0,
        coverageMax: 20000,
        risk: 30
      }
    ]
  };
  beforeEach(() => {
    store = mockStore({
      data: {
        modules: [],
        cart: [],
        total: 0
      }
    });
    store.dispatch = jest.fn(displayModules);
    wrappedComponent = renderer.create(
      <Provider store={store}>
        <App />
      </Provider>
    );
  });

  it("should render with given state from Redux store", () => {
    expect(wrappedComponent.toJSON()).toMatchSnapshot();
  });
  it("should have an app from Landing Page", () => {
    expect(wrappedComponent.root.findByProps({ className: "app" })).toBeDefined();
  });

  it("should show landing page for default route", () => {
    *debugger;
    expect(wrappedComponent.root.findByProps({ className: "shopping cart icon" })).toBeDefined();*
  });
  it("should show cart page for /cart route", () => {
    history.push("/cart");
    expect(wrappedComponent.root.findByProps({ className: "backward icon" })).toBeDefined();
  });
  it("should redirect to landing page for unmatched 404 routes", () => {
    history.push("/someRandomRoute");
    expect(wrappedComponent.root.findByProps({ className: "shopping cart icon" })).toBeDefined();
  });
  it("should dispatch displayModules action on app mount", async () => {
    const actualAction = await store.dispatch();
    expect(actualAction).toEqual(expectedActions);
  });
});

Но если вы видите тестовый отладчик enter image description here

Дети div с className: body не имеет детей. Вот почему он не может найти компонент Tile. Можете ли вы подсказать, почему дети не нужны для тела? Я видел это раньше, даже я пытался с ферментом, я столкнулся с этой проблемой. Поскольку это компонент Redux, я не могу напрямую создать целевую страницу или компонент Tile для тестирования. Как проверить вложенные элементы?

1 Ответ

3 голосов
/ 20 марта 2020

Вы предоставляете пустой массив для модулей в состоянии избыточности:

store = mockStore({
  data: {
    modules: [], // your modules is empty so no tiles will render
    cart: [],
    total: 0
  }
});

Другая проблема заключается в том, что вы смоделируете store.dispatch, чтобы он больше не изменял хранилище избыточности, даже если отправлено какое-то действие:

store.dispatch = jest.fn(displayModules);

Если вы хотите проверить, было ли отправлено действие, вы можете использовать:

const actions = store.getActions()

, который даст вам все отправленные действия.

Если вы хотите проверить, как ваше приложение отображает данные вашего магазина, можно:

  1. Установить хранилище в тесте:
const existingModules = [ ... ]; // list of modules
store = mockStore({
  data: {
    modules: existingModules, 
    cart: [],
    total: 0
  }
});
Вы можете поиздеваться над использованиемSelector в своем тесте:
const existingModules = [ ... ]; // list of modules
const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue(existingModules)
...