Как я могу имитировать службу в компоненте React, чтобы изолировать модульные тесты в шутку? - PullRequest
1 голос
/ 06 августа 2020

Я пытаюсь провести рефакторинг модульного теста, чтобы изолировать службу, которая вызывает API с использованием ax ios, от компонента, вызывающего службу.

На данный момент служба очень проста:

import axios from 'axios'

export default class SomeService {
  getObjects() {
    return axios.get('/api/objects/').then(response => response.data);
  }
}

Вот фрагмент компонента, который вызывает службу:

const someService = new SomeService();

class ObjectList extends Component {
  state = {
    data: [],
  }

  componentDidMount() {
    someService.getObjects().then((result) => {
      this.setState({
        data: result,
      });
    });
  }

  render() {
    // render table rows with object data
  }
}

export default ObjectList

Я могу проверить, что ObjectList отображает данные, как я ожидал, высмеивая ax ios:

// ...
jest.mock('axios')

const object_data = {
  data: require('./test_json/object_list_response.json'),
};

describe('ObjectList', () => {
  test('generates table rows from object api data', async () => {

    axios.get.mockImplementationOnce(() => Promise.resolve(object_data));

    const { getAllByRole } = render(
      <MemoryRouter>
        <table><tbody><ObjectList /></tbody></table>
      </MemoryRouter>
    );

    await wait();

    // test table contents

  });
});

Все проходит без проблем. В качестве в основном академического c упражнения я пытался выяснить, как имитировать SomeService вместо ax ios, и именно здесь все пошло наперекосяк, потому что я думаю, что недостаточно понимаю внутренности того, что происходит вокруг.

Например, я подумал, поскольку SomeService просто возвращает ответ ax ios, я мог бы аналогичным образом издеваться над SomeService, примерно так:

// ...
const someService = new SomeService();

jest.mock('./SomeService')

const object_data = {
  data: require('./test_json/object_list_response.json'),
};

describe('ObjectList', () => {
  test('generates table rows from object api data', async () => {

    someService.getObjects.mockImplementationOnce(() => Promise.resolve(object_data))

// etc.

Это не срабатывает с ошибкой: Error: Uncaught [TypeError: Cannot read property 'then' of undefined], и ошибка восходит к этой строке из ObjectList:

someService.getObjects().then((result) => {

Что конкретно мне нужно для имитации, чтобы компонент ObjectList мог получить то, что ему нужно, из SomeService, чтобы установить его состояние?

Ответы [ 3 ]

1 голос
/ 06 августа 2020

Проблема с имитацией экземпляров класса состоит в том, что может быть трудно получить доступ к экземпляру класса и его методам без ссылки. Поскольку someService является локальным для компонентного модуля, к нему нельзя получить доступ напрямую.

Без указания c mock, jest.mock('./SomeService') полагается на class automati c mock , который работает неуказанными способами. Вопрос показывает, что разные экземпляры mocked-класса имеют разные getObjects mocked-методы, которые не влияют друг на друга, несмотря на то, что getObjects является методом-прототипом и соответствует new SomeService().getObjects === new SomeService().getObjects в разблокированном классе.

Решение не полагаться на автоматические c насмешки, а заставить его работать так, как ожидалось. Практический способ сделать фиктивный метод доступным за пределами экземпляра класса - это переносить его вместе с имитируемым модулем. Таким образом, mockGetObjects.mockImplementationOnce повлияет на существующий someService. mockImplementationOnce подразумевает, что метод может изменить реализацию позже для каждого теста:

import { mockGetObjects }, SomeService from './SomeService';

jest.mock('./SomeService', () => {
  let mockGetObjects = jest.fn();
  return {
    __esModule: true,
    mockGetObjects,
    default: jest.fn(() => ({ getObjects: mockGetObjects }))
  };
});

...

mockGetObjects.mockImplementationOnce(...);
// instantiate the component

Если метод должен иметь постоянную фиктивную реализацию, это упрощает задачу, поскольку реализация может быть указана в jest.mock. По-прежнему может быть полезно выставить mockGetObjects для утверждений.

1 голос
/ 06 августа 2020

После некоторых проб и ошибок, поигравших с различными подходами, предложенными в документации по шутке , единственное, что, казалось, работало, - это вызов jest.mock() с параметром фабрики модуля, например:

// rename data to start with 'mock' so that the factory can use it
const mock_data = {
  data: require('./test_json/object_list_response.json'),
};

jest.mock('./SomeService', () => {
  return jest.fn().mockImplementation(() => {
    return {
      getObjects: () => {
        return Promise.resolve(mock_data).then(response => response.data)
      }
    };
  });
});

// write tests

Использование mockResolvedValue() не сработало, потому что я не мог связать с ним .then().

Если это приведет кого-нибудь к более элегантному или идиоматическому решению c, я бы приветствую другие ответы.

0 голосов
/ 07 августа 2020

Для потомков другим решением является создание вручную макета в папке __mocks__ (на основе комментария Estus Flask и этой документации ).

./__mocks__/SomeService.js

export const mockGetObjects = jest.fn()

const mock = jest.fn(() => {
    return {getObjects: mockGetObjects}
})

export default mock

Тогда простой вызов jest.mock('./SomeService') работает с реализацией, которая позже будет определена в тесте:

mockGetObjects.mockImplementationOnce(() => {
  return Promise.resolve(object_data).then(response => response.data)
})
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...