Search code examples
unit-testingjestjsnext.jsnock

Jest and HTTP mocking with nock


I was trying to write my first ever Jest test for an endpoint in my Next.js App and this test always passes, no matter, how I try to 'break' it. Which makes me think, I did all wrong. Here is my api/weather.js endpoint:

const url = (city) => `https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${city}`;
export default async function handler(req, res) {
 const { query: { city } } = req
  return fetch(url(city))
    .then((response) => {
      if(response.ok){
        return response.json()
      }
      throw new Error('Response not OK')
    })
    .then((data) => res.status(200).json(data))
    .catch(() => res.status(400).json({message: 'Currently not avaliable'}))
}

So basically my frontend posts a request to api/weather looking somewhat like

const fetchCityData = () => {
    const options = {
      method: `POST`,
    };
    fetch(`/api/weather?city=${city}`, options)
    .then((response) => { ......

I got a task to test the endpoint, which I understand should test api/weather.js and as this is dependent on the external API I'd need to mock the request. I am still a bit lost here. Also, I work with query strings, which I tried to integrate in my jest test, but not really sure what I am doing

import nock from 'nock';
it('should return weather', () => {
    nock('https://api.weatherapi.com/v1')
    .get(`/current.json?key=123434&q=London`)
    .reply(200, { results: [{ temp_c: '18 degrees' }] })
});

Basically what happens in 'real life' is that I type a city into an input into a frontend, that city gets posted to api/weather.js and it will then return a weather for this city. How do I test it? I've been reading about nock and jest for 3 days now, but I really don't get the concept behind it. Also, if i rewrite .reply in the test to 400 or rewrite results test will still pass. Why? What am I doing wrong?


Solution

  • The second parameter you're passing to nock's .reply is going create an interceptor which will alter requests for the time it persists to the host you're setting it up for. That means that by doing this:

        it('should return weather', () => {
           nock('https://api.weatherapi.com/v1')
           .get(`/current.json?key=123434&q=London`)
           .reply(200, { results: [{ temp_c: '18 degrees' }] })
        });
    

    the next fetch call will return an object comprised of it:

       const { results } = await fetchCityData("London");
       expect(results[0].temp_c).toEqual("19 degrees");
    

    That'll pass. This won't:

        const { results } = await fetchCityData("London");
        expect(results[0].temp_c).toEqual("33 degrees");
    
       Expected: "33 degrees"
       Received: "19 degrees"
    

    It's up to you then to decide to fully mock a proper request with all the data you want to mock from WeatherAPi, e.g.:

    [...]
     {
          location: {
            name: 'London',
            region: 'City of London, Greater London',
            country: 'United Kingdom',
            lat: 51.52,
            lon: -0.11,
            tz_id: 'Europe/London',
            localtime_epoch: 1631181607,
            localtime: '2021-09-09 11:00'
          },
          current: {
    [...]
    

    or experiment with Nock's recording options which allow you to go back and forth on data grabbed where you wish to do so.

    Here's a good example at dominicfraser/NockExamples which will cover a few use cases which can come useful to you and give a few pointers, and then go from there!

    Edit 1: Here's some code that should hopefully clarify some stuff:

    import nock from "nock";
    const fetch = require('node-fetch');
    
    const SERVER_HOST = 'http://localhost:3000';
    
    // from WeatherApi.com
    const londonMock = {"location":{"name":"London","region":"City of London, Greater London","country":"United Kingdom",
    "lat":51.52,"lon":-0.11,"tz_id":"Europe/London","localtime_epoch":1631195008,
    "localtime":"2021-09-09 14:43"},"current":{"last_updated_epoch":
    1631194200,"last_updated":"2021-09-09 14:30","temp_c":23,"temp_f":73.4,"is_day":1,
    "condition":{"text":"Partly cloudy","icon":"//cdn.weatherapi.com/weather/64x64/day/116.png","code":1003},
    "wind_mph":11.9,"wind_kph":19.1,"wind_degree":210,"wind_dir":"SSW","pressure_mb":1008,
    "pressure_in":29.77,"precip_mm":0,"precip_in":0,"humidity":69,"cloud":75,"feelslike_c":25,
    "feelslike_f":77,"vis_km":10,"vis_miles":6,"uv":5,"gust_mph":9.6,"gust_kph":15.5}};
    
    const fetchCityData = async (city) => {
      const options = {
        method: `GET`,
      };
      const response = await fetch(`${SERVER_HOST}/api/weather?city=${city}`, options);
      const data = await response.json();
      return data;
    }
    
    it("checks if London's weather equals to 33 degrees on a mocked response", async () => {
        nock(SERVER_HOST)
          .get(`/api/weather?city=London`)
          .reply(200, londonMock);
        const results = await fetchCityData("London");
        expect(results.current.temp_c).toEqual(33);
    });
    
    it("checks if London's weather equals to 23 degrees on a mocked response", async () => {
        nock(SERVER_HOST)
          .get(`/api/weather?city=London`)
          .reply(200, londonMock);
        const results = await fetchCityData("London");
        expect(results.current.temp_c).toEqual(23);
    });