sowasred2012
sowasred2012

Reputation: 735

Testing data returned by axios updates the DOM

I'm attempting to TDD a React component that will make a few API calls, but I'm struggling with how to test that responses affect the DOM as expected. In this example I'm making a request to get a list of posts, updating the state which then updates the DOM to show said list.

This works in reality, but my test fails because it can't find the li element I'm looking for (it finds 0, rather than 1), despite me being able to see the element in the error output.

Where am I going wrong? Feel free to rip my test setup to shreds, I'm just getting to grips with TDDing React components.

// PostList.js

import React from "react";
import axios from "axios";

class PostList extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      posts: []
    };

    this.getPosts = this.getPosts.bind(this);
  }

  async getPosts() {
    return axios.get("/api/posts/").then(response => {
      return response.data.posts;
    });
  }

  componentDidMount() {
    this.getPosts().then(posts => {
      this.setState({
        posts: posts
      });
    });
  }

  render() {
    const posts = this.state.posts.map(post => (
      <li key={post.id}>
        <strong>{post.title}</strong> {post.description}
      </li>
    ));

    return (
      <div>
        <h1>Posts:</h1>
        <ul>{posts}</ul>
      </div>
    );
  }
}

export default PostList;
// PostList.test.js

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

import PostList from "./PostList";

describe("<PostList />", () => {
  let shallowPostList;
  let posts = [{ id: 1, title: "Hello", description: "World" }];

  const getPostsMock = new MockAdapter(axios);

  const PostList = () => {
    if (!shallowPostList) {
      shallowPostList = shallow(<PostList />);
    }

    return shallowPostList;
  };

  getPostsMock
    .onGet("/api/posts/")
    .reply(200, { posts });

  beforeEach(() => {
    shallowPostList = undefined;
  });

  describe("render()", () => {
    it("renders one post item when one post exists", done => {
      const PostListItems = PostList().find("li");

      setTimeout(() => {
        expect(PostListItems).toHaveLength(1);
        done();
      }, 1);
    });
  });
});

Upvotes: 0

Views: 384

Answers (1)

zsgomori
zsgomori

Reputation: 516

I think whether it's TDD or not isn't an essential detail. I'm also a bit confused about your approach.

The solution depends on your testing framework as well. The most popular one for React is Jest, therefore I can come up with a solution that works with that pretty well.

If I were you, I'd separate the async function into an individual file, thus it eases the mocking.

import axios from 'axios';

const getPosts = async () => axios.get('/api/posts/');

export default getPosts;

Let's suppose that you have a PostList component within the usual src folder along with the index.js file.

.
├── src
│   ├── index.js
│   ├── PostList
│       ├── __mocks__
│          ├── GetPosts.js
│       ├── PostList.js
│       ├── PostList.test.js
│       ├── GetPosts.js

The __mocks__ folder is recognized by Jest and works as expected, so long as the file naming convention is being followed:

  • the file name should be the same as the one that is being mocked.

Also pay attention to use jest.mock('...') in the test file.

Based on your mock example, you can define something similar to this in __mocks__/GetPosts.js.

const returnedData = [{
   id: 1,
   title: "Hello",
   description: "World"
}];

const getPosts = jest.fn().mockReturnValueOnce(() => returnedData);

export default getPosts;
// PostList.js 
...
async componentDidMount() {
   const posts = await GetPosts();
   this.setState({
      posts,
   });
}
...

And your test file should be something like this:

import React from 'react';
import { shallow } from 'enzyme';
import PostList from './PostList.js';

jest.mock('./GetPosts.js'); // Here you "tell" to Jest to mock the function.

describe('<PostList />', () => {
   let wrapper;

   beforeAll(async () => {
      wrapper = await shallow(<PostList />);
   });

   describe('componentDidMount', () => {
      it('renders an li tag', () => {
         expect(wrapper.find('li')).toHaveLength(1);
      });
   });
});

Upvotes: 1

Related Questions