Node.js Shopify应用Mocha测试报app.address is not a function错误
问题根因
TypeError: app.address is not a function报错来自两个核心问题:
- 你导入的
server根本不是chai-http可识别的HTTP服务实例:现有代码里Koa服务实例完全定义在app.prepare().then()的异步回调内部,外层没有做导出,index.js实际导出的是app.prepare()返回的Promise对象,这个对象不存在address()方法,chai-http拿到后自然报错。 - 就算正确导出Koa实例,chai-http也需要拿到
server.listen()返回的原生Node.js HTTP Server对象才能正常工作,直接传Koa实例本身也可能出现兼容问题。
修复步骤
1. 调整server.js的导出逻辑
不要把Koa初始化、路由注册的逻辑全塞在异步回调里不对外暴露,调整结构:提前初始化Koa实例和路由,等Next.js准备完成后再启动监听,同时对外暴露可访问的服务实例。
修改后的server.js核心结构如下:
import "@babel/polyfill"; import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth"; import Shopify, { ApiVersion } from "@shopify/shopify-api"; import dotenv from "dotenv"; import "isomorphic-fetch"; import Koa from "koa"; import Router from "koa-router"; import next from "next"; import http from 'http'; const mongoose = require("mongoose"); dotenv.config(); const port = parseInt(process.env.PORT, 10) || 3000; const dev = process.env.NODE_ENV !== "production"; const nextApp = next({ dev }); const handle = nextApp.getRequestHandler(); const AccessToken = {}; const ACTIVE_SHOPIFY_SHOPS = {}; Shopify.Context.initialize({ API_KEY: process.env.SHOPIFY_API_KEY, API_SECRET_KEY: process.env.SHOPIFY_API_SECRET, SCOPES: process.env.SCOPES.split(","), HOST_NAME: process.env.HOST.replace(/https:\/\/|\/$/g, ""), API_VERSION: ApiVersion.January22, IS_EMBEDDED_APP: true, SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(), }); // 提前初始化Koa实例和路由,不要放在异步回调内 const koaServer = new Koa(); const router = new Router(); koaServer.keys = [Shopify.Context.API_SECRET_KEY]; koaServer.use( createShopifyAuth({ async afterAuth(ctx) { const { shop, accessToken, scope } = ctx.state.shopify; AccessToken["access_token"] = accessToken; ctx.cookies.set("accessToken", accessToken, { httpOnly: false, secure: true, sameSite: "none", }); ctx.cookies.set("shop_name", shop, { secure: true, sameSite: "none", httpOnly: false, }); const host = ctx.query.host; ACTIVE_SHOPIFY_SHOPS[shop] = scope; ctx.cookies.set("HOST", host, { secure: true, sameSite: "none", httpOnly: false, }); const shopName = shop.replace("myshopify.com", ""); const formattedShopName = shopName .replace(/[^a-z\d\s]+/gi, "") .replace(/\s+/g, "") .toLowerCase(); const connectionStr = `${process.env.CONNECTION_STRING}${formattedShopName}`; await mongoose .connect(connectionStr, { // 填写你的数据库连接配置 }) .then(async () => { console.log("database connected"); }) .catch((err) => console.log(err)); ctx.redirect(`/?shop=${shop}&host=${host}`); }, }) ); router.get("/customers", async (ctx, next) => { // 填写获取客户的业务逻辑,必须给ctx赋值返回,否则接口会挂起 ctx.body = { success: true } }); const handleRequest = async (ctx) => { await handle(ctx.req, ctx.res); ctx.respond = false; ctx.res.statusCode = 200; }; router.get("(/_next/static/.*)", handleRequest); router.get("/_next/webpack-hmr", handleRequest); router.get("(.*)", verifyRequest(), handleRequest); koaServer.use(router.allowedMethods()); koaServer.use(router.routes()); let httpServer = null; const readyPromise = nextApp.prepare().then(() => { // 非测试环境正常监听端口,测试环境不占端口,交给chai-http处理 if (process.env.NODE_ENV !== 'test') { httpServer = koaServer.listen(port, () => { console.log(`> Ready on http://localhost:${port}`); }); } else { httpServer = http.createServer(koaServer.callback()); } return httpServer; }); // 对外导出初始化Promise和服务实例获取方法 module.exports = { ready: readyPromise, getServer: () => httpServer }
2. 调整测试用例逻辑
Next.js的app.prepare是异步操作,必须等服务完全初始化完成后再执行测试用例,修改appTest.js:
let chai = require("chai"); let chaiHttp = require("chai-http"); const { ready, getServer } = require("../server/index"); chai.should(); chai.use(chaiHttp); describe("GET requests", () => { // 测试套件执行前先等服务初始化完成 before(async () => { await ready; }); describe("GET /customers", () => { it("Success should return true", (done) => { chai.request(getServer()) .get("/customers") .end((err, res) => { console.log('Error => ', err); console.log('Response => ', res); res.body.success.should.equal(true) done(); }); }); }); });
3. 修改测试启动脚本
跑测试时需要指定NODE_ENV=test,避免服务启动时占用3000端口,修改package.json里的test脚本:
"test": "cross-env NODE_ENV=test mocha"
你项目里已经安装了cross-env,不需要额外装依赖。
额外注意事项
- 当前
/customers路由的处理函数是空的,没有任何返回值,就算服务启动成功测试也会超时挂掉,记得补上对应的响应逻辑。 - 测试环境下
verifyRequest中间件会拦截未携带Shopify认证信息的请求,直接返回302跳转,拿不到预期的JSON结果,可以加环境变量判断,测试环境下跳过这个中间件的校验。
内容的提问来源于stack exchange,提问作者sanjidulhaque
相关产品推荐
相关产品推荐

