Làm thế nào để một đơn vị kiểm tra các tuyến đường với Express?


99

Tôi đang trong quá trình học Node.js và đã chơi với Express . Thực sự thích framework; tuy nhiên, tôi đang gặp khó khăn khi tìm cách viết một bài kiểm tra tích hợp / đơn vị cho một tuyến đường.

Có thể kiểm tra đơn vị các mô-đun đơn giản rất dễ dàng và đã được thực hiện với Mocha ; tuy nhiên, các bài kiểm tra đơn vị của tôi với Express không thành công vì đối tượng phản hồi tôi đang chuyển vào không giữ lại các giá trị.

Tuyến đường-Chức năng đang Kiểm tra (tuyến đường / index.js):

exports.index = function(req, res){
  res.render('index', { title: 'Express' })
};

Mô-đun kiểm tra đơn vị:

var should = require("should")
    , routes = require("../routes");

var request = {};
var response = {
    viewName: ""
    , data : {}
    , render: function(view, viewData) {
        viewName = view;
        data = viewData;
    }
};

describe("Routing", function(){
    describe("Default Route", function(){
        it("should provide the a title and the index view name", function(){
        routes.index(request, response);
        response.viewName.should.equal("index");
        });

    });
});

Khi tôi chạy điều này, nó không thành công vì "Lỗi: phát hiện rò rỉ toàn cầu: viewName, data".

  1. Tôi đã sai ở đâu để tôi có thể làm việc này?

  2. Có cách nào tốt hơn để tôi kiểm tra đơn vị mã của mình ở cấp độ này không?

Cập nhật 1. Đoạn mã đã sửa vì ban đầu tôi quên "it ()".

Câu trả lời:


21

Thay đổi đối tượng phản hồi của bạn:

var response = {
    viewName: ""
    , data : {}
    , render: function(view, viewData) {
        this.viewName = view;
        this.data = viewData;
    }
};

Và nó sẽ hoạt động.


2
Đây là đơn vị kiểm tra trình xử lý yêu cầu, không phải là một tuyến đường.
Jason Sebring

43

Như những người khác đã khuyến nghị trong các nhận xét, có vẻ như cách chuẩn để kiểm tra bộ điều khiển Express là thông qua supertest .

Một bài kiểm tra ví dụ có thể trông như thế này:

describe('GET /users', function(){
  it('respond with json', function(done){
    request(app)
      .get('/users')
      .set('Accept', 'application/json')
      .expect(200)
      .end(function(err, res){
        if (err) return done(err);
        done()
      });
  })
});

Ngược lại: bạn có thể kiểm tra toàn bộ ngăn xếp của mình trong một lần.

Nhược điểm: nó cảm thấy và hoạt động giống như thử nghiệm tích hợp.


1
Tôi thích điều này, nhưng có cách nào để khẳng định tên viewName (như trong câu hỏi ban đầu) - hay chúng ta phải xác nhận về nội dung của câu trả lời?
Alex

19
Tôi đồng ý với nhược điểm của bạn, đây không phải là thử nghiệm đơn vị. Điều này dựa vào việc tích hợp tất cả các đơn vị của bạn để kiểm tra các url của ứng dụng của bạn.
Luke H

10
Tôi nghĩ là hợp pháp khi nói rằng một "lộ trình" thực sự là một integrationvà có lẽ các lộ trình thử nghiệm nên được để lại cho các thử nghiệm tích hợp. Ý tôi là, chức năng của các tuyến phù hợp với các lệnh gọi lại xác định của chúng có lẽ đã được kiểm tra bởi express.js; bất kỳ logic nội bộ nào để nhận được kết quả cuối cùng của một lộ trình, lý tưởng nên được mô-đun hóa bên ngoài nó và các mô-đun đó phải được kiểm tra đơn vị. Tương tác của chúng, tức là, tuyến đường, nên được kiểm tra tích hợp. Bạn có đồng ý không?
Aditya MP

1
Đó là thử nghiệm end-to-end. Không nghi ngờ gì nữa.
kgpdeveloper

23

Tôi đã đi đến kết luận rằng cách duy nhất để thực sự kiểm tra đơn vị các ứng dụng express là duy trì nhiều sự tách biệt giữa trình xử lý yêu cầu và logic cốt lõi của bạn.

Do đó, logic ứng dụng của bạn phải nằm trong các mô-đun riêng biệt có thể được requirekiểm tra d và đơn vị và có sự phụ thuộc tối thiểu vào các lớp Yêu cầu và Phản hồi nhanh như vậy.

Sau đó, trong trình xử lý yêu cầu, bạn cần gọi các phương thức thích hợp của các lớp logic cốt lõi của mình.

Tôi sẽ đưa ra một ví dụ sau khi tôi hoàn thành việc cơ cấu lại ứng dụng hiện tại của mình!

Tôi đoán một cái gì đó như thế này? (Vui lòng phân tích ý chính hoặc bình luận, tôi vẫn đang khám phá điều này).

Biên tập

Đây là một ví dụ nhỏ, nội dòng. Xem ý chính để có ví dụ chi tiết hơn.

/// usercontroller.js
var UserController = {
   _database: null,
   setDatabase: function(db) { this._database = db; },

   findUserByEmail: function(email, callback) {
       this._database.collection('usercollection').findOne({ email: email }, callback);
   }
};

module.exports = UserController;

/// routes.js

/* GET user by email */
router.get('/:email', function(req, res) {
    var UserController = require('./usercontroller');
    UserController.setDB(databaseHandleFromSomewhere);
    UserController.findUserByEmail(req.params.email, function(err, result) {
        if (err) throw err;
        res.json(result);
    });
});

3
Theo tôi, đây là mẫu tốt nhất để sử dụng. Nhiều khuôn khổ web trên các ngôn ngữ sử dụng mẫu bộ điều khiển để tách logic nghiệp vụ khỏi chức năng hình thành phản hồi http thực tế. Bằng cách này, bạn chỉ có thể kiểm tra logic chứ không phải toàn bộ quy trình phản hồi http, đây là điều mà các nhà phát triển của khung công tác nên tự kiểm tra. Những thứ khác có thể được kiểm tra trong mô hình này là phần mềm trung gian đơn giản, một số chức năng xác nhận và các dịch vụ kinh doanh khác. DB thử nghiệm kết nối là một loại hoàn toàn khác của thử nghiệm mặc dù
OzzyTheGiant

1
Thật vậy, rất nhiều câu trả lời ở đây thực sự liên quan đến kiểm tra tích hợp / chức năng.
Luke H

19

Cách dễ nhất để kiểm tra HTTP với express là đánh cắp trình trợ giúp http của TJ

Cá nhân tôi sử dụng người trợ giúp của anh ấy

it("should do something", function (done) {
    request(app())
    .get('/session/new')
    .expect('GET', done)
})

Nếu bạn muốn kiểm tra cụ thể đối tượng tuyến đường của mình, thì hãy chuyển các đoạn mô phỏng chính xác

describe("Default Route", function(){
    it("should provide the a title and the index view name", function(done){
        routes.index({}, {
            render: function (viewName) {
                viewName.should.equal("index")
                done()
            }
        })
    })
})

5
bạn có thể sửa liên kết 'helper' không?
Nicholas Murray

16
Dường như cách tiếp cận nhiều up-to-date HTTP kiểm tra đơn vị là sử dụng supertest bởi Visionmedia. Có vẻ như trình trợ giúp http của TJ đã phát triển thành supertest.
Akseli Palén

2
supertest trên github có thể được tìm thấy ở đây
Brandon

@Raynos, bạn có thể giải thích cách bạn nhận được yêu cầu và ứng dụng trong ví dụ của mình không?
jmcollin92

9
Đáng buồn thay, đây là thử nghiệm tích hợp hơn là thử nghiệm đơn vị.
Luke H

8

nếu kiểm thử đơn vị với express 4, hãy lưu ý ví dụ này từ gjohnson :

var express = require('express');
var request = require('supertest');
var app = express();
var router = express.Router();
router.get('/user', function(req, res){
  res.send(200, { name: 'tobi' });
});
app.use(router);
request(app)
  .get('/user')
  .expect('Content-Type', /json/)
  .expect('Content-Length', '15')
  .expect(200)
  .end(function(err, res){
    if (err) throw err;
  });

1

Tôi cũng tự hỏi điều này, nhưng đặc biệt cho các bài kiểm tra đơn vị chứ không phải các bài kiểm tra tích hợp. Đây là những gì tôi đang làm ngay bây giờ,

test('/api base path', function onTest(t) {
  t.plan(1);

  var path = routerObj.path;

  t.equals(path, '/api');
});


test('Subrouters loaded', function onTest(t) {
  t.plan(1);

  var router = routerObj.router;

  t.equals(router.stack.length, 5);
});

Nơi chỉ có routerObj {router: expressRouter, path: '/api'}. Sau đó, tôi tải var loginRouterInfo = require('./login')(express.Router({mergeParams: true}));trong các bộ định tuyến con bằng và sau đó ứng dụng express gọi một hàm init lấy bộ định tuyến express làm tham số. Sau đó, initRouter sẽ gọi router.use(loginRouterInfo.path, loginRouterInfo.router);để gắn kết máy tính con.

Máy tính con có thể được kiểm tra với:

var test = require('tape');
var routerInit = require('../login');
var express = require('express');
var routerObj = routerInit(express.Router());

test('/login base path', function onTest(t) {
  t.plan(1);

  var path = routerObj.path;

  t.equals(path, '/login');
});


test('GET /', function onTest(t) {
  t.plan(2);

  var route = routerObj.router.stack[0].route;

  var routeGetMethod = route.methods.get;
  t.equals(routeGetMethod, true);

  var routePath = route.path;
  t.equals(routePath, '/');
});

3
Điều này trông thực sự thú vị. Bạn có thêm ví dụ về các phần còn thiếu để cho thấy tất cả những thứ này khớp với nhau như thế nào không?
cjbarth

1

Để đạt được thử nghiệm đơn vị thay vì thử nghiệm tích hợp, tôi đã chế nhạo đối tượng phản hồi của trình xử lý yêu cầu.

/* app.js */
import endpointHandler from './endpointHandler';
// ...
app.post('/endpoint', endpointHandler);
// ...

/* endpointHandler.js */
const endpointHandler = (req, res) => {
  try {
    const { username, location } = req.body;

    if (!(username && location)) {
      throw ({ status: 400, message: 'Missing parameters' });
    }

    res.status(200).json({
      location,
      user,
      message: 'Thanks for sharing your location with me.',
    });
  } catch (error) {
    console.error(error);
    res.status(error.status).send(error.message);
  }
};

export default endpointHandler;

/* response.mock.js */
import { EventEmitter } from 'events';

class Response extends EventEmitter {
  private resStatus;

  json(response, status) {
    this.send(response, status);
  }

  send(response, status) {
    this.emit('response', {
      response,
      status: this.resStatus || status,
    });
  }

  status(status) {
    this.resStatus = status;
    return this;
  }
}

export default Response;

/* endpointHandler.test.js */
import Response from './response.mock';
import endpointHandler from './endpointHander';

describe('endpoint handler test suite', () => {
  it('should fail on empty body', (done) => {
    const res = new Response();

    res.on('response', (response) => {
      expect(response.status).toBe(400);
      done();
    });

    endpointHandler({ body: {} }, res);
  });
});

Sau đó, để đạt được kiểm tra tích hợp, bạn có thể giả lập endpointHandler của mình và gọi điểm cuối bằng supertest .


0

Trong trường hợp của tôi, điều duy nhất tôi muốn kiểm tra là liệu trình xử lý bên phải đã được gọi hay chưa. Tôi muốn sử dụng supertest để thúc đẩy sự đơn giản của việc thực hiện các yêu cầu đối với phần mềm trung gian định tuyến. Tôi đang sử dụng Typecript a và đây là giải pháp phù hợp với tôi

// ProductController.ts

import { Request, Response } from "express";

class ProductController {
  getAll(req: Request, res: Response): void {
    console.log("this has not been implemented yet");
  }
}
export default ProductController

Các tuyến đường

// routes.ts
import ProductController  from "./ProductController"

const app = express();
const productController = new ProductController();
app.get("/product", productController.getAll);

Các bài kiểm tra

// routes.test.ts

import request from "supertest";
import { Request, Response } from "express";

const mockGetAll = jest
  .fn()
  .mockImplementation((req: Request, res: Response) => {
    res.send({ value: "Hello visitor from the future" });
  });

jest.doMock("./ProductController", () => {
  return jest.fn().mockImplementation(() => {
    return {
      getAll: mockGetAll,

    };
  });
});

import app from "./routes";

describe("Routes", () => {
  beforeEach(() => {
    mockGetAll.mockImplementation((req: Request, res: Response) => {
      res.send({ value: "You can also change the implementation" });
    });
  });

  it("GET /product integration test", async () => {
    const result = await request(app).get("/product");

    expect(mockGetAll).toHaveBeenCalledTimes(1);

  });



  it("GET an undefined route should return status 404", async () => {
    const response = await request(app).get("/random");
    expect(response.status).toBe(404);
  });
});

Tôi gặp một số vấn đề để làm cho việc chế nhạo hoạt động. nhưng việc sử dụng jest.doMock và thứ tự cụ thể mà bạn thấy trong ví dụ làm cho nó hoạt động.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.