【深入浅出 Node + React 的微服务项目】10. 测试独立的微服务

10. 测试独立的微服务

需要测试的范围

测试的范围是哪些?Example
单独测试一段代码独立的 middleware
测试不同的代码片段如何协同工作从多个中间件到单个请求处理器的请求流(这里用英文更直观,中文属实表达不清晰,Request flowing through multiple middlewares to a request handler)
测试不同组件/模块如何协同工作向服务发出请求,确保数据库的写入是完成了的
测试不同服务如何协同工作在“付款 payment”服务中创建“付款”会影响“订单 order”服务

在这里插入图片描述

⬆ back to top

需要测试的目标

  • 请求的测试

在这里插入图片描述

  • 数据库 model 的测试
    在这里插入图片描述
  • 事件收发的测试
    在这里插入图片描述

⬆ back to top

进行测试的架构

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

⬆ back to top

重构项目的 index

// app.ts
import express from "express";
import "express-async-errors";
import { json } from "body-parser";
import cookieSession from "cookie-session";

import { currentUserRouter } from "./routes/current-user";
import { signinRouter } from "./routes/signin";
import { signoutRouter } from "./routes/signout";
import { signupRouter } from "./routes/signup";
import { errorHandler } from "./middlewares/error-handler";
import { NotFoundError } from "./errors/not-found-error";

const app = express();
app.set("trust proxy", true);
app.use(json());
app.use(
  cookieSession({
    signed: false,
    secure: true,
  })
);

app.use(currentUserRouter);
app.use(signinRouter);
app.use(signoutRouter);
app.use(signupRouter);

app.all("*", async (req, res) => {
  throw new NotFoundError();
});

app.use(errorHandler);

export { app };
// index.ts
import mongoose from "mongoose";

import { app } from "./app";

const start = async () => {
  if (!process.env.JWT_KEY) {
    throw new Error("JWT_KEY must be defined");
  }

  try {
    await mongoose.connect("mongodb://auth-mongo-srv:27017/auth", {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useCreateIndex: true,
    });
    console.log("Connected to MongoDb");
  } catch (err) {
    console.log(err);
  }

  app.listen(3000, () => {
    console.log("Listening on port 3000!");
  });
};

start();

⬆ back to top

将会用到的一些依赖

jest
supertest
mongodb-memory-server

⬆ back to top

测试环境配置

// package.json
  "scripts": {
    "start": "ts-node-dev --poll src/index.ts", 
    "test": "jest --watchAll --no-cache" // --watchAll 观察项目所有测试文件的变化  --no-cache 为了解决 jest 有时候识别不了 TypeScript 的文件变化的问题
  },
  "jest": {
    "preset": "ts-jest",
    "testEnvironment": "node",
    "setupFilesAfterEnv": [
      "./src/test/setup.ts"
    ]
  },
// ./test/setup.ts
import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
import { app } from "../app";

let mongo: any;
beforeAll(async () => {
  process.env.JWT_KEY = "asdfasdf";

  mongo = new MongoMemoryServer();
  const mongoUri = await mongo.getUri();

  await mongoose.connect(mongoUri, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  });
});

beforeEach(async () => {
  // 在进行每一个单元测试之前,都要清空每一个 connection 的数据
  const collections = await mongoose.connection.db.collections();

  for (let collection of collections) {
    await collection.deleteMany({});
  }
});

afterAll(async () => {
  await mongo.stop();
  await mongoose.connection.close();
});

⬆ back to top

第一个测试 测试登录

// signup.test.ts
import request from "supertest";
import { app } from "../../app";

it("returns a 201 on successful signup", async () => {
  return request(app)
    .post("/api/users/signup")
    .send({
      email: "test@test.com",
      password: "password",
    })
    .expect(201);
});
npm run test

在这里插入图片描述

  • 这里报错是因为测试环境里没加 JWT
  • 之前我们是在 每个 pod 里kubectl create secret generic jwt-secret --from-literal=JWT_KEY=xxxxxx
  • 所以需要在 beforeAll 里加 JWT 假装有 JWT 即可
  • process.env.JWT_KEY = "asdfasdf";

⬆ back to top

测试无效输入

// signup.test.ts
import request from "supertest";
import { app } from "../../app";

it("returns a 400 with an invalid email", async () => {
  return request(app)
    .post("/api/users/signup")
    .send({
      email: "alskdflaskjfd",
      password: "password",
    })
    .expect(400);
});

it("returns a 400 with an invalid password", async () => {
  return request(app)
    .post("/api/users/signup")
    .send({
      email: "alskdflaskjfd",
      password: "p",
    })
    .expect(400);
});

it("returns a 400 with missing email and password", async () => {
  await request(app)
    .post("/api/users/signup")
    .send({
      email: "test@test.com",
    })
    .expect(400);

  await request(app)
    .post("/api/users/signup")
    .send({
      password: "alskjdf",
    })
    .expect(400);
});

⬆ back to top

email 需要是唯一的

// signup.test.ts
it("disallows duplicate emails", async () => {
  await request(app)
    .post("/api/users/signup")
    .send({
      email: "test@test.com",
      password: "password",
    })
    .expect(201);

  await request(app)
    .post("/api/users/signup")
    .send({
      email: "test@test.com",
      password: "password",
    })
    .expect(400);
});

⬆ back to top

在测试期间更改节点环境

// signup.test.ts
it("sets a cookie after successful signup", async () => {
  const response = await request(app)
    .post("/api/users/signup")
    .send({
      email: "test@test.com",
      password: "password",
    })
    .expect(201);
  // 测试 cookie 有没有 set 进去,有 cookie 就是 define 的
  expect(response.get("Set-Cookie")).toBeDefined();
});

在这里插入图片描述

  • 为什么会出现这种情况?
  • 在我们 cookie 的配置中,secure 写的是 true,那么就会开启 https
  • supertest 用的是 http 不是 https
  • 所以需要根据当前 process.env.NODE_ENV 来判断要不要开 http 和 https
app.use(
  cookieSession({
    signed: false,
    secure: process.env.NODE_ENV !== 'test'
  })
);

⬆ back to top

测试登录

// signin.test.ts
import request from "supertest";
import { app } from "../../app";

it("fails when a email that does not exist is supplied", async () => {
  await request(app)
    .post("/api/users/signin")
    .send({
      email: "test@test.com",
      password: "password",
    })
    .expect(400);
});

it("fails when an incorrect password is supplied", async () => {
  await request(app)
    .post("/api/users/signup")
    .send({
      email: "test@test.com",
      password: "password",
    })
    .expect(201);

  await request(app)
    .post("/api/users/signin")
    .send({
      email: "test@test.com",
      password: "aslkdfjalskdfj",
    })
    .expect(400);
});

it("responds with a cookie when given valid credentials", async () => {
  await request(app)
    .post("/api/users/signup")
    .send({
      email: "test@test.com",
      password: "password",
    })
    .expect(201);

  const response = await request(app)
    .post("/api/users/signin")
    .send({
      email: "test@test.com",
      password: "password",
    })
    .expect(200);

  expect(response.get("Set-Cookie")).toBeDefined();
});

⬆ back to top

登出测试

  • 我们在登出的时候,直接把 session 设为 null 了
router.post('/api/users/signout', (req, res) => {
  req.session = null;

  res.send({});
});
// signout.test.ts
import request from "supertest";
import { app } from "../../app";

it("clears the cookie after signing out", async () => {
  await request(app)
    .post("/api/users/signup")
    .send({
      email: "test@test.com",
      password: "password",
    })
    .expect(201);

  const response = await request(app)
    .post("/api/users/signout")
    .send({})
    .expect(200);

  expect(response.get("Set-Cookie")[0]).toEqual(
    "express:sess=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; httponly"
  );
});
  • 测试小技巧,打印正确的 response,然后测试 response 即可
    在这里插入图片描述
    ⬆ back to top

测试时遇到的 cookie 不好传递的问题

  • 很正常的一个 测试,我们希望登录然后查看 currentuser
  • 然后不出意外确实是可以看到 response body 中的 currentuser
// current-user.test.ts
import request from "supertest";
import { app } from "../../app";

it("responds with details about the current user", async () => {
  await request(app)
    .post("/api/users/signup")
    .send({
      email: "test@test.com",
      password: "password",
    })
    .expect(201);

  const response = await request(app)
    .get("/api/users/currentuser")
    .send()
    .expect(200);

  console.log(response.body);
});
  • 但是打印出来的是 current null
  • 就说明获取不了 我们的登录状态
    在这里插入图片描述

⬆ back to top

认证测试的解决

  • 因为我们登录状态 session 会话(包含 JWT),是通过 cookie 在客户端里存的,还记得之前 express-session 和 cookie-session 的测试吗?
  • 所以要获取到存登录状态的 cookie,必须在登录的时候获取 response 的 cookie,缓存下来,在验证 currentuser 的时候加上
import request from "supertest";
import { app } from "../../app";

it("responds with details about the current user", async () => {
  const authResponse = await request(app)
    .post("/api/users/signup")
    .send({
      email: "test@test.com",
      password: "password",
    })
    .expect(201);
  const cookie = authResponse.get("Set-Cookie");

  const response = await request(app)
    .get("/api/users/currentuser")
    .set("Cookie", cookie)
    .send()
    .expect(200);

  expect(response.body.currentUser.email).toEqual("test@test.com");
});

⬆ back to top

Auth Helper Function

  • 因为我们之后都希望测试的时候,能拿到会话的cookie
  • 所以可以将这段代码抽出来复用
global.signin = async () => {
  const email = "test@test.com";
  const password = "password";

  const response = await request(app)
    .post("/api/users/signup")
    .send({
      email,
      password,
    })
    .expect(201);

  const cookie = response.get("Set-Cookie");

  return cookie;
};

⬆ back to top

测试没认证的

it("responds with null if not authenticated", async () => {
  const response = await request(app)
    .get("/api/users/currentuser")
    .send()
    .expect(200);

  expect(response.body.currentUser).toEqual(null);
});

⬆ back to top

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值