4

Can't figure out how to intercept the getServerSideProps when using cypress component testing.

Did a lot of research and the best lead links:

https://github.com/cypress-io/cypress/discussions/9328

https://www.youtube.com/watch?v=33Hq41O0bvU

https://glebbahmutov.com/blog/mock-network-from-server/

https://www.youtube.com/watch?v=xdVRVhUUgCI&feature=youtu.be

have this example repo: https://github.com/norfeldt/proper/tree/ssr-stubing

what I try to do is:

cypress/plugins/index.ts

const http = require('http');
const next = require('next');
const nock = require('nock');

// start the Next.js server when Cypress starts
module.exports = async (on, config) => {
  const app = next({ dev: true });
  const handleNextRequests = app.getRequestHandler();
  await app.prepare();

  on('dev-server:start', async () => {
    const customServer = new http.Server(async (req, res) => {
      return handleNextRequests(req, res);
    });
    await new Promise<void>((resolve, reject) => {
      customServer.listen(3000, err => {
        if (err) {
          return reject(err);
        }
        console.log('> Ready on http://localhost:3000');
        resolve();
      });
    });

    return customServer;
  });

  on('task', {
    clearNock() {
      nock.restore();
      nock.clearAll();

      return null;
    },

    async nock({ hostname, method, path, statusCode, body }) {
      nock.activate();

      console.log({ hostname, method, path, statusCode, body });

      method = method.toLowerCase();
      nock(hostname)[method][path].reply(statusCode, body);

      return null;
    },
  });

  return config;
};

components/AddProperty/index.spec.ct.tsx

import { mount } from '@cypress/react';
import Component from './index';

beforeEach(() => {
  cy.task('clearNock');
});

it.only('queries the api', () => {
  cy.fixture('properties').then((properties: Property[]) => {
    cy.task('nock', {
      path: '/api/address?q=*',
      method: 'GET',
      statusCode: 200,
      body: {
        json: function () {
          return [{ id: '42', adressebetegnelse: 'Beverly Hills' } as Partial<Property>];
        },
      },
    });
    cy.intercept('GET', '/api/address?q=*', properties).as('getProperties');

    mount(<Component />);

    cy.contains('Beverly Hills');

    cy.get('input').type('Some address{enter}');

    cy.wait('@getProperties').its('response.statusCode').should('eq', 200);

    properties.forEach(property => {
      cy.contains(property.adressebetegnelse);
    });
  });
});

but it won't even run the tests

enter image description here

Norfeldt
  • 8,272
  • 23
  • 96
  • 152
  • Might be worth having a look at [mocking server for SSR react app e2e tests with cypress.io](https://stackoverflow.com/questions/47631821/mocking-server-for-ssr-react-app-e2e-tests-with-cypress-io) and [How do I intercept server-side api calls by cypress](https://stackoverflow.com/questions/68405988/how-do-i-intercept-server-side-api-calls-by-cypress). – juliomalves Jan 21 '22 at 22:12

1 Answers1

3

You have a component test which uses mount() to compile and host the component. This is effectively a "normal" React test, since mount() from '@cypress/react' is a wrapper for react-testing-library.

As such, you are not testing Next, just React.

Note that getServerSideProps is not explicitly called by your component, so nothing you do in the test or the plugin will test it.


I got your test working using Gleb's example you linked above, substituting your app and creating an integration test which will involve NextJS (and therefore call getServerSideProps).

These are the key thing I had to change

  • move getServerSideProps to a page (I used Home page). NextJS will not call it on a component

  • change the spelling (you had getServersideProps)

  • add a return value to getServerSideProps

  • take out the cy.intercept, because nock task is doing the intercepting

This is the test

it.only('queries the api', () => {

  cy.task('nock', {
    hostname: 'http://localhost:3000',
    method: 'GET',      
    path: '/api/address',
    statusCode: 200,
    body: {
      json: function () {
        return [{ id: '42', adressebetegnelse: 'Beverly Hills' }];
      },
    },
  });

  cy.visit('http://localhost:3000')
  cy.contains('Beverly Hills')       // this is what comes from ssrProps

In the plugins/index I changed the nock interceptor to

nock(hostname)[method](path)
  .query(true)
  .reply((uri, requestBody) => {
    console.log('nock uri', uri)
    return [
      200,
      { id: '42', adressebetegnelse: 'Beverly Hills' }
    ]
  })

where query(true) just accepts any query parameters. Using a callback with .reply() allows a console log to check that it's catching the request.

This is the Home page which fetches the ssrProps.

import PropertyList from '../components/PropertyList/index.js'
import Link from 'next/link'

export default function Home(props) {

  return (
    <div>
      <h1>Portfolio</h1>
      <PropertyList />
      <Link href="/add">Tilføj ejendom</Link>

      <!-- outputting the ssrProps value here -->
      <div>{props.ssrProps.adressebetegnelse}</div>

    </div>
  )
}

export async function getServerSideProps(context) {

  const res = await fetch(`http://localhost:3000/api/address?q=strandvejen`)   
  const data = await res.json()

  return {    
    props: {
      ssrProps: data
    },
  }
}

From comments,

It's not easy convert the integration testing SSR mock requests to the component testing?

I still believe it's impossible and contra to the purpose of each test type. SSR includes the server, to test it you need e2e testing - the clue is in the names.

Component testing is an expanded unit test - it performs it's own mount and ignores the server.

To test SSR in a component test you would have to expand mount() in some way, giving you extra code and potential bugs. IMO pointless when the e2e test is reasonably straight forward and does the job.

Fody
  • 23,754
  • 3
  • 20
  • 37
  • I know I'm using `mount()` but I'm also using `injectDevServer` in the plugin folder on `main` https://github.com/Norfeldt/proper/blob/main/cypress/plugins/index.ts - I fail to see if the `mockServer` example from nuxt would intercept the requests in the `injectDevServer` ?? BTW thx a ton for trying to help me out – Norfeldt Feb 02 '22 at 07:48
  • Ok, you linked the ssr-stubbing branch which does not have `injectDevServer`. So I should use main branch instead? – Fody Feb 02 '22 at 08:21
  • I still think that `mount` won't test in a "Next-ly" way. Do you have any reference to docs on `injectDevServer`? – Fody Feb 02 '22 at 08:22
  • The `mockserver` task for the Nuxt question is generic - but it's similar to the way Gleb fires up a server, only I think it's more concise. (You have to ignore the section on intercepting the page load and subbing hydration, since that looks quite Nuxt specfic). – Fody Feb 02 '22 at 08:24
  • I started out from https://github.com/cypress-io/cypress-component-testing-examples which is where I got the `injectDevServer` also saw some docs on https://docs.cypress.io/guides/component-testing/framework-configuration#Next-js – Norfeldt Feb 02 '22 at 08:27
  • Your test looks easily to run in e2e mode, just sub-in cy.visit() for mount(). If I get time, I'll put it together for you. – Fody Feb 02 '22 at 08:27
  • The repo is mainly a PoC to see if cypress component testing can be used. The `main` branch is working fine, but there has been a discussion regrading intercepting requests done on the server. I made the`SSR-stubing` branch to try out glebs suggestion - but can't make it work – Norfeldt Feb 02 '22 at 08:29
  • (to be a little more precise I started out from https://github.com/cypress-io/cypress-component-testing-examples/pull/80 ) don't think it matter, but anyway – Norfeldt Feb 02 '22 at 08:31
  • Ok, maybe you just need `injectDevServer` in the ssr-stubbing branch? That way you could stick to component testing. – Fody Feb 02 '22 at 08:32
  • I would love to keep with `injectDevServer`, but how would I then intercept the server side requests ? – Norfeldt Feb 02 '22 at 08:35
  • I'm trying out the intercept with mock-server task, but need to look at injectDevServer code to see if it changes mount. If not, e2e test would seem to be the only way to go. Any reason why the test must be a component test? – Fody Feb 02 '22 at 09:03
  • Guess that if the e2e would work with intercepting the SSR components then that could also do. The idea of using component testing is it makes it much better when making reusable components – Norfeldt Feb 02 '22 at 10:05
  • Any luck @Fody ? – Norfeldt Feb 03 '22 at 13:09
  • 1
    I have a "working" test, but it's not using getServersideProps - I have to look into how that's supposed to work. – Fody Feb 03 '22 at 21:21
  • Yes, it's not easy convert the integration testing SSR mock requests to the component testing. – Norfeldt Feb 07 '22 at 11:56
  • it's not easy, right? – Norfeldt Feb 16 '22 at 11:39