Next.js: integration test with custom server

Created on 27 Feb 2017  路  15Comments  路  Source: vercel/next.js

I didn't find an integration test with a custom server, I think it would be useful to add that. I'd like to see in particular how the babel config should be set up to avoid transpiling the server files as they aren't transpiled when started the normal way with "start": "node server", but they will if I simply require the server file from within Jest.

Most helpful comment

Same issue here when running in Jest;

SecurityError: Could not parse url argument "ank" to replaceState against base URL "about:blank".

All 15 comments

You mean adding a integration test to Next.js repo or to your app?
Anyway, we don't transpile on the fly. We always require it from the dist directory. (In Next.js tests)

@arunoda I meant adding an integration test with custom server into the Next.js codebase, so that it also serves as example to everyone. Custom server can't be required from dist directory, as it's a custom server... right?

If it helps, here's how we test our server:

Server:

const next = require('next')
const express = require('express')

const dev = process.env.NODE_ENV !== 'production'
let port = process.env.PORT || 3000

if (process.env.NODE_ENV === 'test') port++

const server = express()
const app = next({ dev, dir: __dirname })

server.get('*', (req, res) => app.getRequestHandler()(req, res))

module.exports = app.prepare()
  .then(() => server.listen(port, () => console.log(`Listening on port ${port}`)))
  .catch(console.error)

Test:

/* eslint-env jest */

import serverPromise from '../'
import fetch from 'node-fetch'

let httpServer

beforeAll(async () => { httpServer = await serverPromise })

it('serves the Next.js app', async () => {
  const res = await fetch(`http://localhost:${httpServer.address().port}`)
  expect(await res.text()).toBe('Welcome to Next.js!')
})

afterAll(() => httpServer.close())

Next.js Mock:

module.exports = () => ({
  prepare: () => Promise.resolve(),
  getRequestHandler: () => (req, res) => res.send('Welcome to Next.js!')
})

Babel Config:

{
  "env": {
    "test": {
      "presets": [
        ["env", {
          "targets": {
            "node": "current"
          }
        }],
        "next/babel"
      ]
    },
    "development": {
      "presets": [
        "next/babel"
      ]
    },
    "production": {
      "presets": [
        "next/babel"
      ]
    }
  }
}

Thanks @migueloller for that! I'll start from your code (which I managed to run) and integrate my own server progressively.

(For this I had to yarn add babel-preset-env which I never used before.)

Just in your example, where/how do you inject the Next mock? And do you also run the server with the real Next?

Also this doesn't test that the prod next works correctly (dev: false) but maybe we can just trust Next.js to be properly tested itself for that part.

@sedubois, I'm using Jest for testing so it handles mocks automatically by having a __mocks__/next.js file. If you don't use Jest I guess you might be able to monkeypatch the module.

I'm glad you discovered babel-preset-env. It's a great tool and you should definitely take a look at it as it will simplify you babel configs.

Just in your example, where/how do you inject the Next mock? And do you also run the server with the real Next?

I've trusted Next.js on this regard for now. I had some trouble running next due to some security errors when fetching the server but I'm sure those can be fixed. If you find a fix I'd love to know!

Also this doesn't test that the prod next works correctly (dev: false) but maybe we can just trust Next.js to be properly tested itself for that part.

Yes, you're right. I guess that you could always do const app = next({ dev: process.env.NODE_ENV === 'development', dir: __dirname }) or some other way to pass the value you want given some environment.

Thanks @migueloller I added a very minimal integration test as suggested: https://github.com/relatenow/relate/blob/e0783c722a31039872ea8d98071638c3099d1930/__tests__/integration/index.test.js

However as you mentioned, I had to mock next.

When I don't mock next and try to build next then run with dev: false, I get this error:

(node:83818) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4): SecurityError
(node:83818) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
  console.error node_modules/next/dist/server/index.js:608
    SecurityError
        at HistoryImpl._sharedPushAndReplaceState (/Users/sdubois/Documents/relate/node_modules/jsdom/lib/jsdom/living/window/History-impl.js:87:15)
        at HistoryImpl.replaceState (/Users/sdubois/Documents/relate/node_modules/jsdom/lib/jsdom/living/window/History-impl.js:72:10)
        at History.replaceState (/Users/sdubois/Documents/relate/node_modules/jsdom/lib/jsdom/living/generated/History.js:95:34)
        at Router.changeState (/Users/sdubois/Documents/relate/node_modules/next/dist/lib/router/router.js:379:31)
        at new Router (/Users/sdubois/Documents/relate/node_modules/next/dist/lib/router/router.js:128:13)
        at renderPage (/Users/sdubois/Documents/relate/node_modules/next/dist/server/render.js:149:25)
        at Function.getInitialProps (/Users/sdubois/Documents/relate/node_modules/next/dist/server/document.js:75:25)
        at _callee$ (/Users/sdubois/Documents/relate/node_modules/next/dist/lib/utils.js:36:30)
        at tryCatch (/Users/sdubois/Documents/relate/node_modules/regenerator-runtime/runtime.js:64:40)
        at Generator.invoke [as _invoke] (/Users/sdubois/Documents/relate/node_modules/regenerator-runtime/runtime.js:355:22)

When I don't build next and run with dev: true, I get:

    TypeError: Cannot read property 'ensurePage' of undefined
        at HotReloader.ensurePage (/Users/sdubois/Documents/relate/node_modules/next/dist/server/hot-reloader.js:617:34)
        at _callee6$ (/Users/sdubois/Documents/relate/node_modules/next/dist/server/render.js:305:32)
        at tryCatch (/Users/sdubois/Documents/relate/node_modules/regenerator-runtime/runtime.js:64:40)
        at Generator.invoke [as _invoke] (/Users/sdubois/Documents/relate/node_modules/regenerator-runtime/runtime.js:355:22)
        at Generator.prototype.(anonymous function) [as next] (/Users/sdubois/Documents/relate/node_modules/regenerator-runtime/runtime.js:116:21)
        at step (/Users/sdubois/Documents/relate/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
        at /Users/sdubois/Documents/relate/node_modules/babel-runtime/helpers/asyncToGenerator.js:35:14
        at F (/Users/sdubois/Documents/relate/node_modules/babel-runtime/node_modules/core-js/library/modules/_export.js:35:28)
        at /Users/sdubois/Documents/relate/node_modules/babel-runtime/helpers/asyncToGenerator.js:14:12
        at ensurePage (/Users/sdubois/Documents/relate/node_modules/next/dist/server/render.js:316:19)
        at _callee3$ (/Users/sdubois/Documents/relate/node_modules/next/dist/server/render.js:111:20)
        at tryCatch (/Users/sdubois/Documents/relate/node_modules/regenerator-runtime/runtime.js:64:40)
        at Generator.invoke [as _invoke] (/Users/sdubois/Documents/relate/node_modules/regenerator-runtime/runtime.js:355:22)
        at Generator.prototype.(anonymous function) [as next] (/Users/sdubois/Documents/relate/node_modules/regenerator-runtime/runtime.js:116:21)
        at step (/Users/sdubois/Documents/relate/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
        at /Users/sdubois/Documents/relate/node_modules/babel-runtime/helpers/asyncToGenerator.js:35:14
        at F (/Users/sdubois/Documents/relate/node_modules/babel-runtime/node_modules/core-js/library/modules/_export.js:35:28)
        at /Users/sdubois/Documents/relate/node_modules/babel-runtime/helpers/asyncToGenerator.js:14:12
        at doRender (/Users/sdubois/Documents/relate/node_modules/next/dist/server/render.js:202:18)
        at renderToHTML (/Users/sdubois/Documents/relate/node_modules/next/dist/server/render.js:363:10)
        at Server._callee13$ (/Users/sdubois/Documents/relate/node_modules/next/dist/server/index.js:590:49)
        at tryCatch (/Users/sdubois/Documents/relate/node_modules/regenerator-runtime/runtime.js:64:40)
        at Generator.invoke [as _invoke] (/Users/sdubois/Documents/relate/node_modules/regenerator-runtime/runtime.js:355:22)
        at Generator.prototype.(anonymous function) [as next] (/Users/sdubois/Documents/relate/node_modules/regenerator-runtime/runtime.js:116:21)
        at step (/Users/sdubois/Documents/relate/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
        at /Users/sdubois/Documents/relate/node_modules/babel-runtime/helpers/asyncToGenerator.js:35:14
        at F (/Users/sdubois/Documents/relate/node_modules/babel-runtime/node_modules/core-js/library/modules/_export.js:35:28)
        at Server.<anonymous> (/Users/sdubois/Documents/relate/node_modules/babel-runtime/helpers/asyncToGenerator.js:14:12)
        at Server.renderToHTML (/Users/sdubois/Documents/relate/node_modules/next/dist/server/index.js:621:23)
        at /Users/sdubois/Documents/relate/server/cache.js:25:15
        at /Users/sdubois/Documents/relate/server/cache.js:31:12
        at Layer.handle [as handle_request] (/Users/sdubois/Documents/relate/node_modules/express/lib/router/layer.js:95:5)
        at trim_prefix (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:312:13)
        at /Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:280:7
        at Function.process_params (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:330:12)
        at next (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:271:10)
        at defaultSessionData (/Users/sdubois/Documents/relate/server/session.js:25:5)
        at Layer.handle [as handle_request] (/Users/sdubois/Documents/relate/node_modules/express/lib/router/layer.js:95:5)
        at trim_prefix (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:312:13)
        at /Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:280:7
        at Function.process_params (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:330:12)
        at next (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:271:10)
        at session (/Users/sdubois/Documents/relate/node_modules/express-session/index.js:454:7)
        at Layer.handle [as handle_request] (/Users/sdubois/Documents/relate/node_modules/express/lib/router/layer.js:95:5)
        at trim_prefix (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:312:13)
        at /Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:280:7
        at Function.process_params (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:330:12)
        at next (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:271:10)
        at urlencodedParser (/Users/sdubois/Documents/relate/node_modules/body-parser/lib/types/urlencoded.js:91:7)
        at Layer.handle [as handle_request] (/Users/sdubois/Documents/relate/node_modules/express/lib/router/layer.js:95:5)
        at trim_prefix (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:312:13)
        at /Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:280:7
        at Function.process_params (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:330:12)
        at next (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:271:10)
        at jsonParser (/Users/sdubois/Documents/relate/node_modules/body-parser/lib/types/json.js:103:7)
        at Layer.handle [as handle_request] (/Users/sdubois/Documents/relate/node_modules/express/lib/router/layer.js:95:5)
        at trim_prefix (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:312:13)
        at /Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:280:7
        at Function.process_params (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:330:12)
        at next (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:271:10)
        at expressInit (/Users/sdubois/Documents/relate/node_modules/express/lib/middleware/init.js:33:5)
        at Layer.handle [as handle_request] (/Users/sdubois/Documents/relate/node_modules/express/lib/router/layer.js:95:5)
        at trim_prefix (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:312:13)
        at /Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:280:7
        at Function.process_params (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:330:12)
        at next (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:271:10)
        at query (/Users/sdubois/Documents/relate/node_modules/express/lib/middleware/query.js:44:5)
        at Layer.handle [as handle_request] (/Users/sdubois/Documents/relate/node_modules/express/lib/router/layer.js:95:5)
        at trim_prefix (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:312:13)
        at /Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:280:7
        at Function.process_params (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:330:12)
        at next (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:271:10)
        at Function.handle (/Users/sdubois/Documents/relate/node_modules/express/lib/router/index.js:176:3)
        at Function.handle (/Users/sdubois/Documents/relate/node_modules/express/lib/application.js:173:10)
        at Server.app (/Users/sdubois/Documents/relate/node_modules/express/lib/express.js:38:9)
        at emitTwo (events.js:106:13)
        at Server.emit (events.js:192:7)
        at parserOnIncoming (_http_server.js:565:12)
        at HTTPParser.parserOnHeadersComplete (_http_common.js:99:23)

@arunoda would you have an idea?

@sedubois Hey, did you have any luck solving TypeError: Cannot read property 'ensurePage' of undefined?

@sedubois I haven't seen the issue but I hope it's not initializing next well.
I hope that's because you said you are mocking next.

@arunoda I'm not mocking next and getting the ensurePage error with dev: true.

dev: false is working properly

I think this should be reopened - I had the same problem (security errors). For some reason it seems like when starting the server with jest, the window location is 'about:blank' (I don't know why). The function getUrl in utils then parses it to 'ank' which is a bug.

Same issue here when running in Jest;

SecurityError: Could not parse url argument "ank" to replaceState against base URL "about:blank".

@robinvdvleuten I was getting the same. The issue is jest runs with in a jsdom environment, so this is breaking next's heuristics. A (workaround?) solution is to run jest with node environment:

jest --env=node

I want to run jest jsdom environment for full mounting support of enzyme. Please reopen this issue

@migueloller Fairly new to next, but I'm having issues with your example. In particular this line:

import serverPromise from '../'

What is serverPromise supposed to be?

@zacharyw, that's the promise exported by the main server file that eventually resolves then the Next.js app is ready.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

timneutkens picture timneutkens  路  3Comments

DvirSh picture DvirSh  路  3Comments

knipferrc picture knipferrc  路  3Comments

kenji4569 picture kenji4569  路  3Comments

YarivGilad picture YarivGilad  路  3Comments