Как проверить, что console.log был вызван в componentDidMount с помощью Jest и Enzyme? - PullRequest
0 голосов
/ 21 февраля 2019

Я пытаюсь проверить случаи, когда мой вызов axios не получает HTTP-ответ 200. Когда axios не получает успешный ответ, он выдает ошибку.Я хочу проверить, что console.log вызывается дважды в этом случае.

Вот фрагмент класса, который я тестирую:

class App extends React.Component {
    ...  
    async componentDidMount() {
        let url = "/api/info/tmp"
        try {
            let response = await axios.get(url);
            ...do stuff
            this.setState(...);
        } catch (e) {
            console.log("Could not get " + url);
            console.log(e);
        }
    }
    ...
}

А вот фрагмент моего теста на шутку

let mockAxios = new MockAdapter(axios);
...
describe("App - componentDidMount() behavior test", () => {
    beforeEach(() => {
        app = shallow(<App />);
    })

    afterEach(() => {
        app = undefined;
        mockAxios.reset();
    });
    ...
    describe("Get " + url + " HTTP response status is not 200", () => {
        beforeAll(() => {
            mockAxios.onGet(url).reply(302, mockData);
        });
        it("Does not set state regardless of response body", () => {
            console.log = jest.fn();
            const state = app.state();
            expect(console.log).toHaveBeenCalledTimes(2);
            expect(state.solutions).toEqual({});
            expect(state.username).toEqual("");
        });
    });
});

Я знаю, что бит console.log = jest.fn() что-то делает, потому что консоль больше не регистрирует ложную ошибку, когда я ее устанавливаю.Однако тест не пройден, потому что Expected mock function to have been called two times, but it was called zero times.

Я пытался переместить console.log = jest.fn() в beforeEach, beforeAll и в качестве глобальной переменной.

UPDATE

Я почти уверен, что это как-то связано со всей происходящей асинхронностью.Если я сделаю это:

    it("Does not set state regardless of response body", async () => {
        console.log = jest.fn();
        await app.instance().componentDidMount();
        expect(console.log).toHaveBeenCalledTimes(2);

        const state = app.state();
        expect(state.solutions).toEqual({});
        expect(state.username).toEqual("");
    });

Тогда тест все равно не пройден, но моя причина изменилась: Expected mock function to have been called two times, but it was called four times. Теперь я просто должен выяснить, почему он вызывался четыре раза, а не дважды.

ОБНОВЛЕНИЕ 2

Я понял, почему console.log вызывался 4 раза!Теперь мне просто нужно выяснить, как мне следует провести рефакторинг моих тестов.Если я закомментирую свою шутку и даже весь модульный тест

    it("Does not set state regardless of response body", async () => {
        //const state = app.state();
        //expect(state.solutions).toEqual({});
        //expect(state.username).toEqual("");
        //expect(console.log).toHaveBeenCalledTimes(2);
    });

, тогда я смогу подсчитать в своей консоли, что в действительности уже есть два разных вызова console.log.shallow(<App />) должно быть уже звонит componentDidMount() или что-то в этом роде.Когда я добавляю app.instance().componentDidMount(), я могу визуально увидеть, что он регистрируется 4 раза.

Ответы [ 3 ]

0 голосов
/ 21 февраля 2019

Я понял это!Вид ... Я не уверен, , почему это работает так, но установка макета в фактическое "это" не сработала.Решение было сделать beforeEach и afterEach

describe("Get " + url + " HTTP response status is not 200", () => {
    beforeAll(() => {
        mockAxios.onGet(url).reply(302, mockData);
    });
    beforeEach(() => {
        console.log = jest.fn();
    });

    afterEach(() => {
        jest.resetAllMocks();
    });
    it("Does not set state regardless of response body", async () => {
        const state = app.state();
        expect(state.solutions).toEqual({});
        expect(state.username).toEqual("");
        expect(console.log).toHaveBeenCalledTimes(2);
    });
});
0 голосов
/ 21 февраля 2019

В идеале вы должны переместить вызов API за пределы componentDidMount в его собственный класс method.Таким образом, он может быть вызван вручную из метода жизненного цикла или из обратного вызова события.Кроме того, вы должны ожидать, что ответ каким-то образом повлияет на ваш пользовательский интерфейс state (пример: отображение сообщения о том, что запрос не выполнен, и повторная попытка).

Следующий пример можно выполнить с помощью .then/.catch вместо async/await.В любом случае, вы работаете с Promises, которые asynchronous, и поэтому им нужно asynchronous тесты.

Примечание : ниже предполагается, что disableLifecycleMethods верно в enzyme адаптер.Кроме того, просто тестирование state изменений (или console.log) немного излишне;вместо этого вы должны проверить, отображается ли компонент на основе текущих state.

Рабочий пример : https://codesandbox.io/s/939w229l9r (включает в себя тесты end to end и integration--- вы можете запустить тесты, нажав на вкладку Tests, расположенную в левом нижнем углу песочницы)


App.js (это будет контейнер , который содержит все соответствующие state и распределяет его по своему children при необходимости)

import React, { Component } from 'react';

class App extends Component {
  state = = {
    error: "", 
    isLoading: true,
    solutions: {}, 
    username: ""
  };

  componentDidMount() {
    this.fetchData("/api/info/tmp");
  }

  fetchData = async (url) => {
    try {
      const res = await axios.get(url);

      ...do stuff

      this.setState({ 
        error: "", 
        isLoading: false, 
        solutions: res.data.solutions, 
        username: res.data.username 
      });
    } catch (err) {
        this.setState({ 
          error: err, 
          isLoading: false, 
          solutions: {}, 
          username: "" 
        });
    }
  }

  render() { ... }
}

App.test.js (предполагается, что вы захотитеend to end тест)

import { shallow } from 'enzyme';
import App from './App';

const timeout = () =>
  new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, 2000);
  });

const initialState = {
  error: "", 
  isLoading: true,
  solutions: {}, 
  username: ""
};

describe("App", () => {
  let wrapper;
  beforeEach(() => {
    wrapper = shallow(<App />);
    wrapper.setState({ ...initialState });
  });

  afterAll(() => {
     wrapper.unmount();
  });

  it("sets data to state based upon successful API call", async () => { 
   wrapper.instance().fetchData("/api/info/tmp");
   await timeout();

   wrapper.update();
   expect(wrapper.state('isLoading')).toBeFalsy();
   expect(wrapper.state('solutions')).toEqual({ somedata });
   expect(wrapper.state('username')).toEqual("Some User");
  });

  it("displays an error upon unsuccessful API call", async () => { 
   wrapper.instance().fetchData("/api/bad/url");
   await timeout();

   wrapper.update();
   expect(wrapper.state('isLoading')).toBeFalsy();
   expect(wrapper.state('solutions')).toEqual({});
   expect(wrapper.state('username')).toEqual("");
   expect(wrapper.state('error')).toEqual("No data found.");
  });
});

App.test.js (предполагается, что вы хотите integration тест)

import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import React from "react";
import { shallow } from "enzyme";
import App from "../App";

const solutions = [{ ... }, { ... }];
const username = "Some User"

const mockAxios = new MockAdapter(axios);

const initialState = {
  error: "", 
  isLoading: true,
  solutions: {}, 
  username: ""
};

describe("App", () => { 
  let wrapper;
  beforeEach(() => {
    wrapper = shallow(<App />);
    wrapper.setState({ ...initialState });
  });

  afterEach(() => {
    mock.reset();
  });

  afterAll(() => {
    mock.restore();
    wrapper.unmount();
  });

  it("displays an error upon unsuccessful API call", async () => { 
    try {
       mockAxios.onGet("/users").networkErrorOnce();

       await axios.get("users");

     } catch (err) {
       const error = err.toString();
       wrapper.setState({ 
         error, 
         isLoading: false,
         solutions: {}, 
         username: ""
       });

       wrapper.update();
       expect(wrapper.state('isLoading')).toBeEqual(error);
       expect(wrapper.state('isLoading')).toBeFalsy();
       expect(wrapper.state('solutions')).toEqual({});
       expect(wrapper.state('username')).toEqual("");
     } 
  });

  it("sets data to state based upon successful API call", async () => {
    try {
      mockAxios.onGet("/users").reply(200, { solutions, username });

      const res = await axios.get("users");

      wrapper.setState({ 
        error: "", 
        isLoading: true,
        solutions: res.data.solutions, 
        username: res.data.username
      });

      wrapper.update();
      expect(wrapper.state('isLoading')).toBeFalsy();
      expect(wrapper.state('solutions')).toEqual(solutions);
      expect(wrapper.state('username')).toEqual(username);
    } catch (e) {
        console.log(e);
    } 
  });
});
0 голосов
/ 21 февраля 2019

Обновленный ответ

Так как похоже, что вы уже знаете, что делаете с имитаторами, возможно, проблема связана с componentDidMount().

Я считаю, что ваш призыв к shallow(<App />) будет уже один раз вызовет приложение componentDidMount() (что означает, что ваш console.log будет вызван дважды там).

Затем вы впоследствии позвоните app.instance().componentDidMount() - то есть выпозвоните componentDidMount() снова (что означает, что ваш console.log будет снова дважды туда звонить).

Итак, всего ... 4 звонка на console.log.

Надеюсь, что это направит вас в правильном направлении ...

Оригинальный ответ

На самом деле ваш вопрос выглядит очень похоже на [этот вопрос StackOverFlow о том, как "Как смоделировать консоль, когдаон используется сторонней библиотекой? "

Вы можете использовать Jest mock functions to spyOn объект global.console.

Например, ваш тест может выглядеть так:


// Setup jest to spy on the console
const consoleSpy = jest.spyOn(global.console, 'log')

describe('App - componentDidMount() behavior test', () => {
  beforeEach(() => {
    jest.resetAllMocks()  // reset your consoleSpy state back to initial
    app = shallow(<App />)
  })

  ...

      it('Does not set state regardless of response body', () => {
        const spy = jest.spyOn(global.console, 'log')
        const state = app.state()
        expect(consoleSpy).toHaveBeenCalledTimes(2)
        expect(state.solutions).toEqual({})
        expect(state.username).toEqual('')
      })
  ...
...