A Request to be Mocked

We’re starting the day with a failure.

The application test fails because it is trying to make a network request and, as everyone knows, we don’t want to make network requests in unit tests.

I suppose I could change the Application.test to use shallow() instead of mount() but then we wouldn’t actually be testing very much — and we’ll have to deal with this network request problem eventually.

Axios comes with an API for mocking network requests but that all feels a bit low-level to me.

What I’d like to do is refactor the code so that it is easy to switch out the network layer entirely when we are testing. Let’s introduce an API layer with a function to fetch posts.

We’ll start by extracting a method fetchPost(), then convert it to be an async function and await the response in componentDidMount(). Then, finally, we’ll move the function to its own file.

I’ll just show the final result.

// posts/api.js
import * as axios from 'axios'

export function fetchPost(id) {
  const request = {
    url: `http://localhost:3000/posts/${id}.json`
  }
  return axios(request).then(response => response.data)
}
  async componentDidMount() {
    const post = await fetchPost(1)
    this.setState({post})
  }

EXPLAIN PROMISES

EXPLAIN ASYNC

We’re not done yet because I’d like to introduce one more level of indirection. I want to abstract away the call to the server partly because this code will end up being used in a bunch of places and partly because that’s where I want to introduce my cleavage point for mocking.

All problems in computer science can be solved by another level of indirection.

David Wheeler
// remote/server.js
import * as axios from 'axios'

export const server = {send}
function send(url) {
  const request = {url}
  return axios(request).then(response => response.data)
}

// posts.api.js
import {server} from 'remote/server'

export function fetchPost(id) {
  return server.send(`/posts/${id}.json`)
}

There. That looks much nicer and now we are finally ready to mock out that server connection In Application.test.jsx.

import {Application} from 'application/Application'
import {server} from 'remote/server'

describe('The application', () => {
  server.send = jest.fn()

  beforeEach( () => {
    const post = {
      id: 1,
      title: 'React on Rails',
      body: 'I can use React with Rails.',
    }
    server.send.mockReturnValue(post)
  })

  test('shows the first blog post', async () => {
    const component = await mount(<Application />)

    expect(component.find('.site-name').text()).toEqual('Blogging')
    expect(component.find('.title').text()).toEqual('React on Rails')
    expect(component.find('.body').text()).toEqual('I can use React with Rails.')

    expect(server.send).toBeCalledWith('/posts/1.json')
  })
})

EXPLAIN jest.fn()

ADD A PASSED banner

Published by

Kevin

I've been coding professionally for 30 years, 25 of those in Silicon Valley. I'm home now in beautiful Bristol, England.

Leave a Reply

Your email address will not be published. Required fields are marked *