Bir birim, Express ile rotaları nasıl test eder?


101

Node.js öğrenme sürecindeyim ve Express ile uğraşıyorum . Gerçekten çerçeve gibi; ancak, bir rota için nasıl birim / entegrasyon testi yazacağımı bulmakta zorlanıyorum.

Basit modülleri birim test edebilmek kolaydır ve bunu Mocha ile yapıyoruz ; ancak, geçtiğim yanıt nesnesi değerleri korumadığından Express ile birim testleri başarısız oluyor.

Test Altındaki Yol İşlevi (yollar / index.js):

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

Birim Test Modülü:

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

    });
});

Bunu çalıştırdığımda, "Hata: genel sızıntılar algılandı: görünümAdı, veri" hatası veriyor.

  1. Bunu çalıştırabilmek için nerede yanlış yapıyorum?

  2. Kodumu bu seviyede birim test etmenin daha iyi bir yolu var mı?

Güncelleme 1. "it ()" yi ilk başta unuttuğumdan beri düzeltilmiş kod pasajı.

Yanıtlar:


21

Yanıt nesnenizi değiştirin:

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

Ve işe yarayacak.


4
Bu, bir yol değil, bir istek işleyiciyi test eden birimdir.
Jason Sebring

44

Diğerlerinin yorumlarda önerdiği gibi, Ekspres denetleyicileri test etmenin kanonik yolu süper testten geçiyor gibi görünüyor .

Örnek bir test şöyle görünebilir:

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

Ters: tüm yığınızı tek seferde test edebilirsiniz.

Dezavantajı: biraz entegrasyon testi gibi hissediyor ve davranıyor.


1
Bunu beğendim, ancak viewName'i iddia etmenin bir yolu var mı (orijinal soruda olduğu gibi) - yoksa cevabın içeriği hakkında ileri sürmemiz gerekir mi?
Alex

22
Senin dezavantajına katılıyorum, bu birim testi değil. Bu, uygulamanızın url'lerini test etmek için tüm birimlerinizin entegrasyonuna dayanır.
Luke H

11
Bir "yol" un gerçekten bir "yol" olduğunu söylemenin yasal olduğunu düşünüyorum integrationve belki de test yolları entegrasyon testlerine bırakılmalıdır. Demek istediğim, tanımlanmış geri aramalarıyla eşleşen yolların işlevselliği muhtemelen express.js tarafından zaten test edilmiştir; Bir rotanın nihai sonucunu elde etmek için herhangi bir dahili mantık ideal olarak bunun dışında modülerleştirilmeli ve bu modüller birim test edilmelidir. Etkileşimleri, yani yol, entegrasyon testine tabi tutulmalıdır. Katılır mısın
Aditya MP

1
Uçtan uca testtir. Şüphesiz.
kgpdeveloper

25

Hızlı uygulamaları gerçekten birim test etmenin tek yolunun, istek işleyicileri ile çekirdek mantığınız arasında çok fazla ayrım sağlamak olduğu sonucuna vardım.

Bu nedenle, uygulama mantığınız required ve birim test edilebilen ayrı modüllerde olmalı ve bu tür Hızlı Talep ve Yanıt sınıflarına minimum bağımlılığa sahip olmalıdır.

Daha sonra istek işleyicilerinde çekirdek mantık sınıflarınızın uygun yöntemlerini çağırmanız gerekir.

Mevcut uygulamamı yeniden yapılandırmayı bitirdiğimde bir örnek vereceğim!

Sanırım bunun gibi bir şey ? (Özü veya yorum yapmaktan çekinmeyin, hala bunu araştırıyorum).

Düzenle

İşte satır içi küçük bir örnek. Daha ayrıntılı bir örnek için özüne bakı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
Bence bu, kullanılacak en iyi model. Farklı dillerdeki birçok web çerçevesi, iş mantığını gerçek http yanıt oluşturma işlevinden ayırmak için denetleyici modelini kullanır. Bu şekilde, tüm http yanıt sürecini değil, yalnızca mantığı test edebilirsiniz; bu, çerçeve geliştiricilerinin kendi başlarına test etmesi gereken bir şeydir. Bu modelde test edilebilecek diğer şeyler, basit ara yazılımlar, bazı doğrulama işlevleri ve diğer iş hizmetleridir. DB bağlantı testi tamamen farklı bir test türüdür
OzzyTheGiant

1
Nitekim, buradaki yanıtların çoğu gerçekten entegrasyon / işlevsel testlerle ilgilidir.
Luke H

Doğru cevap bu. Express'e değil, mantığınızı test etmeye odaklanmalısınız.
esmiralha

19

HTTP'yi express ile test etmenin en kolay yolu, TJ'nin http yardımcısını çalmaktır

Ben şahsen onun yardımcısını kullanıyorum

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

Rotalar nesnenizi özel olarak test etmek istiyorsanız, doğru örnekleri verin

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
"yardımcı" bağlantıyı düzeltebilir misin?
Nicholas Murray

16
Görünüşe göre HTTP birim testine daha güncel yaklaşım, Visionmedia tarafından süper test kullanmaktır . Görünüşe göre TJ'nin http yardımcısı süper teste dönüştü.
Akseli Palén

2
github'daki süper test burada
Brandon

@Raynos, örneğinizde nasıl bir istek ve uygulama aldığınızı açıklar mısınız?
jmcollin92

9
Ne yazık ki bu, birim testinden çok entegrasyon testidir.
Luke H

8

ekspres 4 ile birim testi yaparsanız, bu örneği gjohnson'dan not edin :

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

Bunu da merak ediyordum ama özellikle birim testleri için, entegrasyon testleri için değil. Şu anda yaptığım şey bu

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

RouterObj sadece nerede {router: expressRouter, path: '/api'}. Daha sonra alt yönlendiricileri yüklerim var loginRouterInfo = require('./login')(express.Router({mergeParams: true}));ve ardından ekspres uygulama, ekspres yönlendiriciyi bir parametre olarak alan bir başlatma işlevini çağırır. İnitRouter daha sonra alt router.use(loginRouterInfo.path, loginRouterInfo.router);yönlendiriciyi bağlamayı çağırır.

Alt gruplayıcı aşağıdakilerle test edilebilir:

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
Bu gerçekten ilginç görünüyor. Tüm bunların nasıl birbirine uyduğunu göstermek için eksik parçalara dair başka örnekleriniz var mı?
cjbarth

1

Entegrasyon testi yerine birim testi gerçekleştirmek için, istek işleyicisinin yanıt nesnesiyle alay ettim.

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

Ardından, entegrasyon testini gerçekleştirmek için endpointHandler'ınızla dalga geçebilir ve uç noktayı süper test ile çağırabilirsiniz .


0

Benim durumumda test etmek istediğim tek şey doğru kişinin aranıp aranmadığıdır. Yönlendirme ara yazılımına istekte bulunmanın basitliğinden yararlanmak için süper test kullanmak istedim. Typescript a kullanıyorum ve bu benim için çalışan çözüm

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

Rotalar

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

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

Testler

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


Alay etmenin işe yaraması için bazı sorunlarım vardı. ancak jest.doMock ve örnekte gördüğünüz belirli sıranın kullanılması, çalışmasını sağlar.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.