Search code examples
javascriptreactjsnext.jscypressnock

Nock JS mocking tool produces error `Right-hand side of 'instanceof' is not an object`


I am trying to use this tool called Nock JS

https://www.npmjs.com/package/nock

with a Next JS, React demo app tested using Cypress

• Next JS 13.4

• Cypress 12.17.1

• React 18.2

e2e/homepage.cy.js



describe('weather app spec', () => {
  it('can load the current weather', () => {


    const nock = require('nock')

    const scope = nock('https://api.openweathermap.org')
      .get('/data/2.5/weather')
      .reply(200, {
      "coord":  {
           "lon": -73.9629,
           "lat": 40.6884
      },
      "weather"  :
        [{"id": 211, "main": "scattered clouds", "description": "scattered clouds", "icon": "11d"}, {
          "id": 500,
          "main": "Rain",
          "description": "scattered clouds",
          "icon": "10d"
      }],
      "base" : "stations",
      "main":
      {
        "temp": 299.25,
        "feels_like": 299.25,
        "temp_min": 296.15,
        "temp_max": 303.46,
        "pressure": 1014,
        "humidity": 75
      }

    })

    cy.visit('/')
    cy.get('#__next').should('contain', "Click to get your weather")
    cy.contains('Get Weather').click()

    cy.contains("Current weather is scattered clouds")
  })
})

If the test does not have nock, the test passes (expected behavior):

enter image description here

Of course, I don't want to call the Open weather API every time i run the test, and now the test is hard-coded to "scattered clouds" which will change as the weather changes.

So I want Nock to mock the HTTP response from the external API, fixing it in place for all test runs

However, if I add the line scope = nock('https://api.openweathermap.org') (you see above), I get:

Right-hand side of 'instanceof' is not an object

enter image description here

Although I don't think it is related to why Nock is giving me this error, for reference, here is implementation code:


import {useRouter} from 'next/router'
import {useState, useEffect, useRef} from 'react'

export async function getServerSideProps(context) {
  // Fetch data from external API
  const OPEN_WEATHER_MAP_API_KEY = process.env.OPEN_WEATHER_APP_API_KEY

  const query = context.query

  const lat = query.lat
  const long = query.long

  if (lat && long) {
    const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${long}&appid=${OPEN_WEATHER_MAP_API_KEY}`;

    const res = await fetch(url)
      .then(res => res.json())
      .then(
        (result) => {
          if (Number(result.cod) === 200) {
            // success result
            const weatherSummary = result.weather[0].description;
            const k_temp = result.main.temp;
            return {forecastSuccess: true,  weatherSummary, temp: k_temp}
          } else {
            return {forecastSuccess: false}
          }
        }
      )
    return {props: res}
  } else {
    return {props: {}}
  }
}


export default function Home({forecastSuccess, weatherSummary, temp}) {
  const router = useRouter();

  const [lat, setLat] = useState(undefined);
  const [long, setLong] = useState(undefined);

  const getLocationAndWeather =  () => {
    navigator.geolocation.getCurrentPosition(async (location) => {
      setLat(location.coords.latitude)
      setLong(location.coords.longitude)
    })
  }

  useEffect(() => {
    if (lat && long) {
      refreshData();
    }
  }, [lat, long])
  const refreshData = () => {
    router.replace(`${router.asPath}?lat=${lat}&long=${long}`);
  }

  return (
    <>
      <p>Click to get your weather:</p>
      <br />
      <input type={"submit"} onClick={ getLocationAndWeather } data-test-id={'get-weather-button'} value={"Get Weather"}/>

      <h1>
        { forecastSuccess && `Current weather is ${weatherSummary}`}
      </h1>
      <h2>
        { forecastSuccess && `${temp} °F`}
      </h2>
      <br />
    </>
  )
}

Solution

  • NockJS is a Node library, so most likely you can't import it into the test (which runs in the browser) and run it there.

    I have a semi-answer here Mocking Next.js getInitialProps in _app which might suit your need.

    Essentially,

    • In NextJs SSR makes it near impossible to mock API requests in Cypress because the server performs the request before Cypress is able to open the web page.

    • You can fairly easily set up "static" mocks with NextJs custom server, but that only allows one set of mock data.

    • In theory you could rig the mock server to start and stop from inside the test using cy.exec() and before cy.visit() you would set up the mock data to suit that particular test.

    • Gleb Bahmutov has an example using NockJs from a Cypress task, but it's written in Cypress v9 and I could not get it to upgrade to the latest version. But it's a viable approach if you want to use the older version, and also a good example of using NockJs with Cypress that you may be able to adapt.


    NockJs not compatable with NextJs SSR?

    One thing I forgot to mention is that NextJs show the experimental NodeJs fetch() in their example (see above link)

    import { NextPageContext } from 'next'
     
    Page.getInitialProps = async (ctx: NextPageContext) => {
      const res = await fetch('https://api.github.com/repos/vercel/next.js')
      ...
    

    But Nock says

    Warning nock is currently not compatible with Node's experimental native fetch implementation.
    See #2397

    I mention this in case you are following the NextJs example.

    You can use NodeJs http as an alternative, but that would require you to downgrade your app.