Bagaimana satu unit menguji rute dengan Express?


99

Saya sedang dalam proses mempelajari Node.js dan telah bermain-main dengannya Express . Benar-benar menyukai kerangka kerjanya; namun, saya mengalami kesulitan untuk mengetahui cara menulis tes unit / integrasi untuk suatu rute.

Mampu menguji unit modul sederhana itu mudah dan telah dilakukan bersama Mocha ; namun, pengujian unit saya dengan Express gagal karena objek respons yang saya teruskan tidak mempertahankan nilainya.

Route-Function Under Test (routes / index.js):

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

Modul Uji Unit:

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");
        });

    });
});

Ketika saya menjalankan ini, gagal untuk "Kesalahan: kebocoran global terdeteksi: viewName, data".

  1. Di mana kesalahan saya sehingga saya bisa mendapatkan ini bekerja?

  2. Apakah ada cara yang lebih baik bagi saya untuk menguji kode saya pada level ini?

Perbarui 1. Potongan kode yang diperbaiki karena saya awalnya lupa "it ()".

Jawaban:


21

Ubah objek respons Anda:

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

Dan itu akan berhasil.


4
Ini adalah pengujian unit penangan permintaan, bukan rute.
Jason Sebring

43

Seperti yang direkomendasikan orang lain dalam komentar, sepertinya cara kanonik untuk menguji pengontrol Express sudah selesai supertest .

Tes contoh mungkin terlihat seperti ini:

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()
      });
  })
});

Kelebihan: Anda dapat menguji seluruh tumpukan Anda sekaligus.

Kelemahan: rasanya dan bertindak seperti pengujian integrasi.


1
Saya suka ini, tetapi apakah ada cara untuk menegaskan viewName (seperti pada pertanyaan awal) - atau apakah kita harus menegaskan pada konten tanggapan?
Alex

21
Saya setuju dengan sisi negatif Anda, ini bukan pengujian unit. Ini bergantung pada integrasi semua unit Anda untuk menguji url aplikasi Anda.
Luke H

10
Saya pikir legal untuk mengatakan bahwa "rute" adalah benar-benar sebuah integration, dan mungkin rute pengujian harus diserahkan kepada tes integrasi. Maksud saya, fungsionalitas rute yang cocok dengan callback yang ditentukan mungkin sudah diuji oleh express.js; logika internal apa pun untuk mendapatkan hasil akhir dari sebuah rute, idealnya harus dimodulasi di luarnya, dan modul tersebut harus diuji unit. Interaksi mereka, yaitu, rute, harus diuji integrasi. Apakah anda setuju
Aditya MP

1
Ini adalah pengujian ujung ke ujung. Tanpa keraguan.
kgpdeveloper

25

Saya sampai pada kesimpulan bahwa satu-satunya cara untuk benar-benar menguji unit aplikasi ekspres adalah dengan mempertahankan banyak pemisahan antara penangan permintaan dan logika inti Anda.

Jadi, logika aplikasi Anda harus berada dalam modul terpisah yang dapat diuji required dan unit, dan memiliki ketergantungan minimal pada kelas Permintaan dan Respons Ekspres seperti itu.

Kemudian di penangan permintaan Anda perlu memanggil metode yang sesuai dari kelas logika inti Anda.

Saya akan memberikan contoh setelah saya selesai merestrukturisasi aplikasi saya saat ini!

Saya kira sesuatu seperti ini? (Jangan ragu untuk membagi intinya atau berkomentar, saya masih mengeksplorasi ini).

Edit

Berikut adalah contoh kecil, sebaris. Lihat intinya untuk contoh yang lebih detail.

/// 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
Menurut saya, ini adalah pola terbaik untuk digunakan. Banyak kerangka kerja web lintas bahasa menggunakan pola pengontrol untuk memisahkan logika bisnis dari fungsionalitas pembentukan respons http yang sebenarnya. Dengan cara ini, Anda hanya dapat menguji logika dan bukan keseluruhan proses respons http, yang merupakan sesuatu yang harus diuji sendiri oleh pengembang framework. Hal lain yang dapat diuji dalam pola ini adalah middlewares sederhana, beberapa fungsi validasi, dan layanan bisnis lainnya. Pengujian konektivitas DB adalah jenis pengujian yang berbeda
OzzyTheGiant

1
Memang, banyak jawaban di sini yang benar-benar berkaitan dengan pengujian integrasi / fungsional.
Luke H

Inilah jawaban yang tepat. Anda harus fokus pada pengujian logika Anda, bukan Express.
esmiralha

19

Cara termudah untuk menguji HTTP dengan express adalah dengan mencuri http helper TJ

Saya pribadi menggunakan pembantunya

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

Jika Anda ingin menguji objek rute Anda secara khusus, teruskan tiruan yang benar

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
bisakah Anda memperbaiki tautan 'pembantu'?
Nicholas Murray

16
Tampaknya pendekatan yang lebih mutakhir untuk pengujian unit HTTP adalah menggunakan supertest oleh Visionmedia. Sepertinya http helper TJ telah berevolusi menjadi supertest.
Akseli Palén

2
supertest di github dapat ditemukan di sini
Brandon

@Raynos Bisakah Anda menjelaskan bagaimana Anda mendapatkan permintaan dan aplikasi dalam contoh Anda?
jmcollin92

9
Sayangnya ini adalah pengujian integrasi daripada pengujian unit.
Luke H

8

jika unit testing dengan express 4 note contoh ini dari 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

Saya juga bertanya-tanya tentang ini, tetapi secara khusus untuk pengujian unit dan bukan pengujian integrasi. Inilah yang saya lakukan sekarang,

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);
});

Dimana routerObj itu adil {router: expressRouter, path: '/api'}. Saya kemudian memuat di subrouter dengan var loginRouterInfo = require('./login')(express.Router({mergeParams: true}));dan kemudian aplikasi ekspres memanggil fungsi init yang mengambil router ekspres sebagai parameter. InitRouter kemudian memanggilrouter.use(loginRouterInfo.path, loginRouterInfo.router); untuk memasang subrouter.

Subrouter dapat diuji dengan:

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
Ini terlihat sangat menarik. Apakah Anda memiliki lebih banyak contoh dari bagian yang hilang untuk menunjukkan bagaimana semua ini cocok?
cjbarth

1

Untuk mencapai pengujian unit alih-alih pengujian integrasi, saya mengejek objek respons dari penangan permintaan.

/* 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);
  });
});

Kemudian, untuk mencapai pengujian integrasi, Anda dapat memalsukan endpointHandler dan memanggil titik akhir dengan supertest .


0

Dalam kasus saya, satu-satunya yang ingin saya uji adalah jika pawang kanan telah dipanggil. Saya ingin menggunakan supertest untuk memanfaatkan kesederhanaan membuat permintaan ke middleware perutean. Saya menggunakan Typecript a dan ini adalah solusi yang berhasil untuk saya

// 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

Rute

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

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

Tesnya

// 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);
  });
});

Saya memiliki beberapa masalah untuk membuat ejekan bekerja. tetapi menggunakan jest.doMock dan urutan spesifik yang Anda lihat di contoh membuatnya berfungsi.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.