2012-03-01 10 views
76

私はNode.jsを学び、Expressで遊んでいます。フレームワークが本当に好きですが、ルートの単体/統合テストを書く方法を理解するのが難しいです。1台のユニットがExpressでルートをテストする方法は?

単純なモジュールを単体テストできることは簡単で、Mochaで実行しています。しかし、私が渡している応答オブジェクトは値を保持していないので、Expressでの単体テストは失敗します。テスト(ルート/ index.js)の下で

ルート機能:

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

ユニットテストモジュール:私はこれを実行すると

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

    }); 
}); 

、それは「エラーのため失敗します。グローバルリークが検出されました:viewName、data "

  1. ここで私はこの作業を行うことができますか?

  2. 私のコードをこのレベルで単体テストするには良い方法がありますか?私が最初に忘れてしまったので、

更新 1.修正したコードスニペット "それを()"。

答えて

18
あなたの応答オブジェクトに変更

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

をそして、それは動作します。

+1

ありがとうございました。それが何か簡単だと分かっていました。 – JamesEggers

18

急行でHTTPをテストする最も簡単な方法は、あなたが特にあなたのルートオブジェクトをテストしたい場合は、正しいモックで

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

あなたは「ヘルパー」リンクを修正できますか? –

+0

@NicholasMurray [test-server](https://github.com/Raynos/test-server) – Raynos

+14

HTTPユニットテストの最新のアプローチは、[supertest](https:// github .com/visionmedia/supertest)によるVisionmedia。それはまた、TJのhttpヘルパーがsupertestに進化したようです。 –

26
を渡し、 TJ's http helper

私はpersonally use his helper

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

を盗むためにあります

他の人がコメントでお勧めしているように、Expressコントローラをテストする標準的な方法は、supertest

サンプルテストは次のようになります。

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

アップサイド:あなたは一度にあなたのスタック全体をテストすることができます。

不利な点:統合テストのように感じています。

+1

私はこれが好きですが、(元の質問のように)viewNameをアサートする方法がありますか?または、レスポンスの内容を主張する必要がありますか? – Alex

+10

私はあなたの欠点に同意します、これは単体テストではありません。これは、すべてのユニットを統合してアプリケーションのURLをテストすることに依存しています。 –

+4

「ルート」は実際には「統合」であり、おそらくテストルートは統合テストに任せておくべきだと私は思うのです。つまり、定義されたコールバックにマッチするルートの機能は、おそらく既にexpress.jsによってテストされています。ルートの最終的な結果を得るための内部ロジックは理想的にはモジュール化されていなければならず、それらのモジュールはユニットテストされるべきです。それらの相互作用、すなわち経路は、統合テストされるべきである。同意しますか? –

15

本当にテストエクスプレスアプリケーションを単体テストする唯一の方法は、リクエストハンドラとコアロジックの間の分離を多く維持することです。

したがって、アプリケーションロジックは、require dとユニットテスト済みで、Express RequestクラスとResponseクラスに依存しない独立したモジュールである必要があります。

リクエストハンドラでは、コアロジッククラスの適切なメソッドを呼び出す必要があります。

私の現在のアプリケーションの再構築が完了したら、私は一例を挙げましょう!

私はthis?のようなものだと思います(要点やコメントを自由に書き留めてください。私はまだこれを探っています)。

編集

ここでは、インライン小さな例です。より詳細な例については、the gistを参照してください。

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

場合gjohnsonからエクスプレス4メモこの例のユニットテスト:私はこれも思っていた

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

が、具体的には、ユニットテストではなく統合テストのためには。これは私が

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がちょうど{router: expressRouter, path: '/api'}で、今やっているものです。 私はvar loginRouterInfo = require('./login')(express.Router({mergeParams: true}));でサブルーチンをロードしてから、expressアプリケーションは、expressルータを引数としてinit関数を呼び出します。その後、initRouterはrouter.use(loginRouterInfo.path, loginRouterInfo.router);を呼び出してサブルーチンをマウントします。

サブルータがでテストすることができます:ユニットテストの代わりに、統合テストを達成するために

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

これは本当に面白そうです。これがどのように一緒に収まるかを示す欠けた作品の例がありますか? – cjbarth

1

、私がリクエストハンドラのレスポンスオブジェクトを嘲笑しました。

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

次に、統合テストを達成するために、あなたはあなたのendpointHandlerを模擬し、supertestでエンドポイントを呼び出すことができます。

関連する問題