hoangvvo / next-connect

The TypeScript-ready, minimal router and middleware layer for Next.js, Micro, Vercel, or Node.js http/http2
https://www.npmjs.com/package/next-connect
MIT License
1.63k stars 65 forks source link

How to test NextJS endpoints #84

Open yegorkay opened 4 years ago

yegorkay commented 4 years ago

Looking at your other repo, how would you go about testing an endpoint that uses middleware, or endpoints, in general, using next-connect?

https://github.com/hoangvvo/nextjs-mongodb-app/blob/master/pages/api/users/%5BuserId%5D/index.js#L8

A potential test:

import { createServer } from 'vercel-node-server'
import listen from 'test-listen'
import axios from 'axios'
import handler from '.'

let server
let url

beforeAll(async () => {
  server = createServer(handler)
  url = await listen(server)
})

afterAll(() => {
  server.close()
})

it('should return the expected response', async () => {
  const response = await axios.get(url, { params: { userId: 'SOME_ID' } })
  expect(response.data).toBeTruthy()
})

Doing something like this returns a 500. Maybe it's just me who is lacking testing knowledge. Any help would be appreciated.

hoangvvo commented 4 years ago

These two can help https://www.npmjs.com/package/light-my-request or https://www.npmjs.com/package/supertest

Using the above, you don't have to start and close the server.

yegorkay commented 4 years ago

Thanks for the reply! I tried setting up a test for the file mentioned above, and realize it's the database and session middlewares causing the 500 error. 200 happens when you comment both of them out of here. You would then have to change the endpoint to not use Mongo like so to pass the test since the middleware isn't applied:

import nextConnect from 'next-connect';
import middleware from '../../../../middlewares/middleware';
import { getUser } from '../../../../lib/db';

const handler = nextConnect();

handler.use(middleware);

// dumb API without mongo
handler.get(async (req, res) => {
  res.send(`hello ${req.query.userId}`);
});

export default handler;
// working test, assuming no Mongo middleware applied
import http from 'http'
import fetch from 'isomorphic-unfetch'
import listen from 'test-listen'
import { apiResolver } from 'next/dist/next-server/server/api-utils'
import handler from '.';

const USER_ID = 'SOME_ID' // to be an actual id once 500 is resolved

describe(`/users/${USER_ID}`, () => {
  test('responds 200', async () => {

    const requestHandler = (req, res) => apiResolver(req, res, undefined, handler)
    const server = http.createServer(requestHandler)
    const url = await listen(server)

    const response = await fetch(`${url}/${USER_ID}`)
    expect(response.status).toBe(200)
    return server.close()
  })
})

Seems like this issue doesn't pertain to this repo, but more towards the nextjs-mongo-db-app repo. I can move this there. I've been trying to figure out how to get integration testing to work there, but I haven't been getting very far. Any ideas as to why mongo causes this to fail?

hoangvvo commented 4 years ago

Oh I see. This should have been mentioned clearer in next-connect doc. next-connect does not add anything by default so things like query parsing (req.query) and helpers like (res.send) are not available. That why errors were thrown in your case.

Nope, wrong assumption, you are using apiResolver so it should work. Did you manage to get the error message?

yegorkay commented 4 years ago

Still getting a 500 sadly. Here are the changes I made if you want to see what's going on:

https://github.com/yegorkay/nextjs-mongodb-app/tree/feature/testing

ljosberinn commented 4 years ago

@yegorkay hey sorry I didn't check in here for a while - I have a whole testing setup tailored for nextjs here: https://github.com/ljosberinn/personal-react-boilerplate/blob/master/testUtils/lambda.ts

and a few tests: https://github.com/ljosberinn/personal-react-boilerplate/tree/master/src/server/__tests__

leosuncin commented 3 years ago

My approach to test is https://github.com/leosuncin/literate/blob/master/tests/pages/api/auth/register.spec.ts
I use @shelf/jest-mongodb to run MongoDB in-memory, but basically I use node-mocks-http to mock req and res objects

leosuncin commented 3 years ago

Another idea is to use supertest

import http from 'http';

import { apiResolver } from 'next/dist/next-server/server/api-utils';
import supertest from 'supertest';

import registerHandler from 'pages/api/auth/register';

jest.setTimeout(10e3);

describe('[POST] /api/auth/register', () => {
  let server;

  beforeEach(async () => {
    const requestHandle = (request, response) =>
      apiResolver(
        request,
        response,
        undefined,
        registerHandler,
        {},
        true,
      );
    server = http.createServer(requestHandler);
  });

  afterEach(() => {
    server.close();
  });

  it('creates a new user', async () => {
    const body = {
      name: 'John Doe',
      email: 'johndoe@example.com',
      password: 'ji32k7au4a83',
    };
    const result = await supertest(server)
      .post('/api/auth/register')
      .send(body)
      .expect(201)
      .expect('Content-Type', /json/);

    expect(result.body).toBeDefined();
  });

  it.each([
    {
      name: '',
      email: '',
      password: '',
    },
    {
      name: 'a',
      email: 'email',
      password: '123456',
    },
  ])('validate the body %p', async (body) => {
    const result = await supertest(server)
      .post('/api/auth/register')
      .send(body)
      .expect(422)
      .expect('Content-Type', /json/);

    expect(result.body).toBeDefined();
  });
});

It has the advantage of no need to start a server

mactkg commented 2 years ago

166 helps me when I write a test using node-mocks-http.

You can use #166 branch like this

npm i --save https://github.com/jakeorr/next-connect#c837eee

and add "postinstall" script to package.json

{
  "scripts": {
    "postinstall": "cd node_modules/next-connect; npm i; npm run build"
  }
}
nath-green commented 2 years ago

Is there a way to test with Jest?

This isn't my post but it is the same issue I'm facing: https://stackoverflow.com/questions/67961149/how-do-i-test-a-next-js-api-route-which-uses-next-connect

When using next-connect I get a timeout error. When using without, the tests work as expected.

Cheers!

chrisrabe commented 1 year ago

@nath-green I use next-connect and using leosuncin's solution worked for me. It no longer has the jest timeout issue

TotomInc commented 1 year ago

If someone need some guidance about how to write unit-tests (with Jest) for your middlewares, I've written a blog-post about it.

If you need to test an API route endpoint from end-to-end, I recommend using next-test-api-route-handler (ntarh)

mtbrault commented 1 year ago

I suggest you to use this pacakge: https://www.npmjs.com/package/nextjs-http-supertest. It allows you to build an http server which will link endpoints with your nextJS handler.

TotomInc commented 1 year ago

I suggest you to use this pacakge: https://www.npmjs.com/package/nextjs-http-supertest. It allows you to build an http server which will link endpoints with your nextJS handler.

How is it better against ntarh? ntarh seems to be already established solution for Next.js endpoint unit-testing. It takes cares of all the boring unit-testing setup and doesn't use an HTTP server. It directly executes the endpoint function which seems to be more efficient and performant.

mtbrault commented 1 year ago

I suggest you to use this pacakge: https://www.npmjs.com/package/nextjs-http-supertest. It allows you to build an http server which will link endpoints with your nextJS handler.

How is it better against ntarh? ntarh seems to be already established solution for Next.js endpoint unit-testing. It takes cares of all the boring unit-testing setup and doesn't use an HTTP server. It directly executes the endpoint function which seems to be more efficient and performant.

As you said, ntarh is a solution for unit-testing. My package is a solution for integration-testing, this is not the same thing.

In unit testing you are only verifying that you're handler is working well, but you don't know if the correct handler is reach for a given endpoint. The aim of integration-testing, is to simulate the context of a browser or any other client performing an http request