5

Component to test

class Carousel extends React.Component {
  state = {
    slides: null
  }

  componentDidMount = () => {
    axios.get("https://s3.amazonaws.com/rainfo/slider/data.json").then(res => {
      this.setState({ slides: res.data })
    })
  }

  render() {
    if (!slides) {
      return null
    }

    return (
      <div className="slick-carousel">
        ... markup trancated for bravity
      </div>
    )
  }
}

export default Carousel

Test

import React from "react"
import renderer from "react-test-renderer"
import axios from "axios"
import Carousel from "./Carousel"

const slides = [
  {
    ID: "114",
    REFERENCE_DATE: "2018-07-02",
    ...
  },
  {
    ID: "112",
    REFERENCE_DATE: "2018-07-06",
    ...
  },
  ...
]

jest.mock("axios")

it("", () => {
  axios.get.mockImplementationOnce(() => Promise.resolve({ data: slides }))

  const tree = renderer.create(<Carousel />).toJSON()
  expect(tree).toMatchSnapshot()
})

snapshot only records null, since at the moment of execution I suppose state.slides = null.

Can't put my finger on how to run expectations after axios done fetching the data.

Most of the samples online either use enzyme, or show tests with async functions that return promises. I couldn't find one that would show example only using jest and rendered component.

I tried making test function async, also using done callback, but no luck.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
AndreiMotinga
  • 1,024
  • 17
  • 24

2 Answers2

3

in short:

it("", async () => {
  axios.get.mockImplementationOnce(() => Promise.resolve({ data: slides }))

  const tree = renderer.create(<Carousel />);
  await Promise.resolve();
  expect(tree.toJSON()).toMatchSnapshot()
})

should do the job

in details: besides you have mocked call to API data is still coming in async way. So we need toMatchSnapshot call goes to end of microtasks' queue. setTimeout(..., 0) or setImmediate will work too but I've found await Promise.resolve() being better recognizable as "everything below is coming to end of queue"

[UPD] fixed snippet: .toJSON must be after awaiting, object it returns will never be updated

skyboyer
  • 22,209
  • 7
  • 57
  • 64
  • thanks for the help and good explanation. I was able to come up with ` Promise.resolve(renderer.create()).then(tree => { expect(tree.toJSON()).toMatchSnapshot() })`, that while I don't get what is doing seemed to be working. Your code, which I ended up using, is much clearer and nicer. Thank you. – AndreiMotinga Nov 29 '18 at 19:48
  • Does this work too if you use `shallow` instead of renderer? – zhrgci May 27 '20 at 08:02
  • @Zheng-rongCai if it's shallow rendering and XHR call should happen in some nested child... that will not be rendered because it's shallow rendering - than no, it will not work. But otherwise it should work – skyboyer May 27 '20 at 16:20
0

The accepted answer started to fail the next day. After some tweaking, this seems to be working:

import React from "react"
import renderer from "react-test-renderer"
import axios from "axios"
import Carousel from "./Carousel"

jest.mock("axios")
const slides = sampleApiResponse()
const mockedAxiosGet = new Promise(() => ({ data: slides }))
axios.get.mockImplementation(() => mockedAxiosGet)

// eventhough axios.get was mocked, data still comes anychrnonously,
// so during first pass state.slides will remain null
it("returns null initally", () => {
  const tree = renderer.create(<Carousel />).toJSON()
  expect(tree).toMatchSnapshot()
})

it("uses fetched data to render carousel", () => {
  const tree = renderer.create(<Carousel />)
  mockedAxiosGet.then(() => {
    expect(tree.toJSON()).toMatchSnapshot()
  })
})

function sampleApiResponse() {
  return [
    {
      ID: "114",
      REFERENCE_DATE: "2018-07-02",
      ...
    },
    {
      ID: "114",
      REFERENCE_DATE: "2018-07-02",
      ...
    },
  ]
}
Larz
  • 1,156
  • 1
  • 8
  • 22
AndreiMotinga
  • 1,024
  • 17
  • 24
  • promise `new Promise(() => ({ data: slides }))` will never be resolved. you need `Promise.resolve()` factory method. using constructor `new Promise()` you need to get resolver as an argument and call it like `new Promise(resolver => resolver({ data: slides }))` but `Promise.resolve()` does exactly the same while having better readability – skyboyer Nov 30 '18 at 20:48