Тестирование слушателя Socket.io в React с помощью Enzyme - PullRequest
1 голос
/ 28 сентября 2019

Я хочу проверить, добавляет ли messageListener данные к состоянию массива messages .Однако я получил несколько блоков по этому вопросу.1. Не удается протестировать методы внутри компонента без состояния 2. Перехватчики React нельзя использовать вне функции компонента.

Я не могу понять, как обойти это?Должен ли я использовать другой подход для тестирования этого?Должен ли я извлечь его в пользовательский хук?Я новичок как в ферментных, так и в реактивных хуках.Я в основном хочу проверить, если с учетом того, что socket.io получает событие MESSAGE, компонент добавит другой элемент в массив сообщений для отображения внутри MessageFeed.

Полный проект также можно увидеть на https://github.com/Hyllesen/react-chat

App.js

import React from "react";
import UsernameInput from "components/UsernameInput";
import MessageFeed from "components/MessageFeed";
import io from "socket.io-client";
import * as eventTypes from "eventTypes";

function joinWithUsername(username) {
  const socket = io("http://localhost:3001");
  socket.emit(eventTypes.USER_JOIN, { username });
  socket.on(eventTypes.MESSAGE, messageListener);
}

function messageListener(data, setMessages) {
  setMessages([...messages, data]);
}

const App = () => {
  const [messages, setMessages] = React.useState([]);

  return (
    <div className="App">
      <UsernameInput onSubmit={joinWithUsername} />
      <MessageFeed messages={messages} />
    </div>
  );
};

export default App;
export { joinWithUsername, messageListener };

App.test.js

import React from "react";
import App, { joinWithUsername, messageListener } from "../App";
import { shallow } from "enzyme";
import io from "socket.io-client";
import * as eventTypes from "eventTypes";

let wrapper, setState, useStateSpy;
jest.mock("socket.io-client", () => {
  const emit = jest.fn();
  const on = jest.fn();
  const socket = { emit, on };
  return jest.fn(() => socket);
});

describe("App", () => {
  beforeEach(() => {
    wrapper = shallow(<App />);
    setState = jest.fn();
    useStateSpy = jest.spyOn(React, "useState");
    useStateSpy.mockImplementation(init => [init, setState]);
  });

  afterEach(() => jest.clearAllMocks());

  it("has a usernameinput component", () => {
    expect(wrapper.find("UsernameInput").length).toBe(1);
  });

  it("has a joinWithUsername function", () => {
    const usernameInput = wrapper.find("UsernameInput").props();
    console.log(usernameInput.onSubmit);
    expect(usernameInput.onSubmit).toBe(joinWithUsername);
  });

  it("connect to socket.io server", () => {
    joinWithUsername("Bobby");
    expect(io).toHaveBeenCalledWith("http://localhost:3001");
  });

  it("emits the USER_JOIN event", () => {
    joinWithUsername("John");
    const socket = io();
    expect(socket.emit).toHaveBeenCalledWith(eventTypes.USER_JOIN, {
      username: "John"
    });
  });

  it("listens for the MESSAGE event", () => {
    joinWithUsername("John");
    const socket = io();
    expect(socket.on).toHaveBeenCalledWith(eventTypes.MESSAGE, messageListener);
  });

  it("Appends a message to state when message is received", () => {
    const testData = {
      from: "John",
      message: "Hello everyone!"
    };
    messageListener(testData);
    expect(setState).toHaveBeenCalledWith(testData); //ReferenceError: setMessages is not defined
  });
});

1 Ответ

0 голосов
/ 29 сентября 2019

К настоящему времени ваш код не работает как целое число:

function messageListener(data, setMessages) {
  setMessages([...messages, data]);
}

const App = () => {
  const [messages, setMessages] = React.useState([]);
...
};

видите, messageListener пытается вызвать setMessages, который объявлен как локальная переменная в другой функции.

Итаквсе должно быть внутри App:

const App = () => {
  const [messages, setMessages] = React.useState([]);

  function joinWithUsername(username) {
    const socket = io("http://localhost:3001");
    socket.emit(eventTypes.USER_JOIN, { username });
    socket.on(eventTypes.MESSAGE, messageListener);
  }

  function messageListener(data, setMessages) {
    setMessages([...messages, data]);
  }


  return (
    <div className="App">
      <UsernameInput onSubmit={joinWithUsername} />
      <MessageFeed messages={messages} />
    </div>
  );
};

Теперь поговорим о тесте.Если вы пересмотрите свой подход, вам будет гораздо легче писать тесты.Я имею в виду, чтобы общаться, вызывая callback props и смоделированную функцию, и проверять результат рендеринга и только вызовы mocks.Нет доступа к состоянию, переменным локали или насмешкамДоступ к Props и макетам должно быть достаточно.Вызов io.on.mock.calls[0][1] может показаться сложным, но это безопасно, поскольку мы подтвердили, что пропущен только один прослушиватель, поэтому нам не нужно заботиться, является ли он прослушивателем eventTypes.MESSAGE или нет.Имея более сложный компонент, вероятно, я бы посмеялся io.client, чтобы быть более умным, чтобы я мог ioMocked.simulateEmitForListenerByType(eventTypes.MESSAGE); или искать существующий пакет .

...