Как мне протестировать методы компонентов в компоненте React, которые определены как стрелочные функции (свойства класса)?

Я могу отлично тестировать методы класса, используя шпионов и Component.prototype. Однако многие из моих методов класса являются свойствами класса, потому что мне нужно использовать this (для this.setState и т. Д.), А поскольку привязка в конструкторе очень утомительна и выглядит некрасиво, использование стрелочных функций, на мой взгляд, намного лучше. Компоненты, которые я создал с использованием свойств класса, работают в браузере, поэтому я знаю, что моя конфигурация babel верна. Ниже представлен компонент, который я пытаюсь протестировать:

    //Chat.js
    import React from 'react';
    import { connect } from 'react-redux';

    import { fetchThreadById, passMessageToRedux } from '../actions/social';
    import withLogin from './hoc/withLogin';
    import withTargetUser from './hoc/withTargetUser';
    import withSocket from './hoc/withSocket';
    import ChatMessagesList from './ChatMessagesList';
    import ChatForm from './ChatForm';

    export class Chat extends React.Component {
        state = {
            messages : [],
        };
        componentDidMount() {
            const { auth, targetUser, fetchThreadById, passMessageToRedux } = this.props;
            const threadId = this.sortIds(auth._id, targetUser._id);
            //Using the exact same naming scheme for the socket.io rooms as the client-side threads here
            const roomId = threadId;
            fetchThreadById(threadId);
            const socket = this.props.socket;
            socket.on('connect', () => {
                console.log(socket.id);
                socket.emit('join room', roomId);
            });
            socket.on('chat message', message => passMessageToRedux(message));
            //socket.on('chat message', message => {
            //    console.log(message);
            //    this.setState(prevState => ({ messages: [ ...prevState.messages, message ] }));
            //});
        }

        sortIds = (a, b) => (a < b ? `${a}_${b}` : `${b}_${a}`);

        render() {
            const { messages, targetUser } = this.props;
            return (
                <div className='chat'>
                    <h1>Du snakker med {targetUser.social.chatName || targetUser.info.displayName}</h1>
                    <ChatMessagesList messages={messages} />
                    <ChatForm socket={this.props.socket} />
                </div>
            );
        }
    }
    const mapStateToProps = ({ chat: { messages } }) => ({ messages });

    const mapDispatchToProps = dispatch => ({
        fetchThreadById    : id => dispatch(fetchThreadById(id)),
        passMessageToRedux : message => dispatch(passMessageToRedux(message)),
    });

    export default withLogin(
        withTargetUser(withSocket(connect(mapStateToProps, mapDispatchToProps)(Chat))),
    );

    Chat.defaultProps = {
        messages : [],
    };

А вот и тестовый файл:

//Chat.test.js
import React from 'react';
import { shallow } from 'enzyme';
import { Server, SocketIO } from 'mock-socket';

import { Chat } from '../Chat';
import users from '../../fixtures/users';
import chatMessages from '../../fixtures/messages';

let props,
    auth,
    targetUser,
    fetchThreadById,
    passMessageToRedux,
    socket,
    messages,
    wrapper,
    mockServer,
    spy;

beforeEach(() => {
    window.io = SocketIO;
    mockServer = new Server('http://localhost:5000');
    mockServer.on('connection', server => {
        mockServer.emit('chat message', chatMessages[0]);
    });
    auth = users[0];
    messages = [ chatMessages[0], chatMessages[1] ];
    targetUser = users[1];
    fetchThreadById = jest.fn();
    passMessageToRedux = jest.fn();
    socket = new io('http://localhost:5000');
    props = {
        mockServer,
        auth,
        messages,
        targetUser,
        fetchThreadById,
        passMessageToRedux,
        socket,
    };
});

afterEach(() => {
    mockServer.close();
    jest.clearAllMocks();
});

test('Chat renders correctly', () => {
    const wrapper = shallow(<Chat {...props} />);
    expect(wrapper).toMatchSnapshot();
});

test('Chat calls fetchThreadById in componentDidMount', () => {
    const wrapper = shallow(<Chat {...props} />);
    const getThreadId = (a, b) => (a > b ? `${b}_${a}` : `${a}_${b}`);
    const threadId = getThreadId(auth._id, targetUser._id);
    expect(fetchThreadById).toHaveBeenLastCalledWith(threadId);
});

test('Chat calls componentDidMount', () => {
    spy = jest.spyOn(Chat.prototype, 'componentDidMount');
    const wrapper = shallow(<Chat {...props} />);
    expect(spy).toHaveBeenCalled();
});

test('sortIds correctly sorts ids and returns threadId', () => {
    spy = jest.spyOn(Chat.prototype, 'sortIds');
    const wrapper = shallow(<Chat {...props} />);
    expect(spy).toHaveBeenCalled();
});

Предпоследний тест, который проверяет, был ли вызван componentDidMount (не метод класса), выполняется без ошибок, как и все другие тесты, кроме последнего. В последнем тесте Jest выдает следующую ошибку:

FAIL  src\components\tests\Chat.test.js
  ● sortIds correctly sorts ids and returns threadId

    Cannot spy the sortIds property because it is not a function; undefined given instead

      65 |
      66 | test('sortIds correctly sorts ids and returns threadId', () => {
    > 67 |     spy = jest.spyOn(Chat.prototype, 'sortIds');
      68 |     const wrapper = shallow(<Chat {...props} />);
      69 |     expect(spy).toHaveBeenCalled();
      70 | });

      at ModuleMockerClass.spyOn (node_modules/jest-mock/build/index.js:699:15)
      at Object.<anonymous> (src/components/tests/Chat.test.js:67:16)

Мне сказали, что я могу использовать mount из фермента вместо shallow, а затем использовать Chat.instance вместо Chat.prototype, но, насколько я понимаю, если я это сделаю, фермент также будет воспроизводить детей Chat, а я, конечно, не хочу этого. На самом деле я пытался использовать mount, но затем Jest начал жаловаться на connect(ChatForm), что store не имеет store ни в его контексте, ни в реквизитах (ChatForm подключен к redux, но мне нравится тестировать свои компоненты, связанные с redux, импортируя неподключенный компонент и издевательски используя redux хранить). Кто-нибудь знает, как тестировать свойства класса на компонентах React с помощью Jest и Enzyme? Заранее спасибо!


person Christoffer Corfield Aakre    schedule 31.03.2018    source источник
comment
Я не уверен, почему вы используете синтаксис инициализатора свойств для sortIds, когда он не использует this. Кроме того, какой аспект вы тестируете, что componentDidMount вызвало метод или что он вычислил действительный результат для своих входных данных? Кажется, что последнее в случае вашего описания теста, и в этом случае его можно протестировать отдельно от создаваемого класса.   -  person Dave Meehan    schedule 31.03.2018
comment
Вы совершенно правы, он не использует this! Он действительно использовал это, но тогда мои тесты не работали, и во время отладки я удалил из него this, но сейчас я вставлю его обратно :). Спасибо за комментарий, но теперь я все прояснил, и все работает. Хорошего дня!   -  person Christoffer Corfield Aakre    schedule 31.03.2018


Ответы (1)


Даже если рендеринг неглубокий, вы можете вызвать метод wrapper.instance().

it("should call sort ids", () => {
    const wrapper = shallow(<Chat />);
    wrapper.instance().sortIds = jest.fn();
    wrapper.update();    // Force re-rendering 
    wrapper.instance().componentDidMount();
    expect(wrapper.instance().sortIds).toBeCalled();
 });
person Agney    schedule 31.03.2018
comment
Спасибо! Это как раз то, что я искал! Я приму твой ответ. - person Christoffer Corfield Aakre; 31.03.2018