mercredi 8 juillet 2015

Express 4 testing routes that render a handlebars template

I'm pulling my hairs out trying TDD my express app. There isn't a bunch of helpful documentation for testing apps that are a bit more complex. For every single test I write I feel like I'm googling and trying for multiple hours before I have something I feel is acceptable. I started working on a controller yesterday and I've written 1 line of production code: res.render().. Enough about personal problems, it's help the poor guy time!

Right now I'm stuck testing my routes that render a handlebars template. I want to know if an entire route ended up rendering('foo'). I would absolutely love to be able to spy on res.render(). I've tried injecting middleware that replaces the res object with a spy:

-- simplified code --

var ResponseStub = {
  _handle: function(req, res, next) {
    if (!this.active) {
      return next();
    }
    res.render = sinon.spy();  // Replace a method with a spy.
    return next();
  },

  install: function(app) {
    this.active = true;
    return this.app._router.stack.unshift({  // Insert the custom middleware at the very beginning
      match: function() {
        return true;
      },
      path: '',
      handle_request: this._handle.bind(this),
      _id: 'response.stub'
    });
  }
}

Let's assume there's a simple route to '/foo' that renders a hbs template

app.get('/foo', function(req, res) { res.render('foo'); );

In my test I then install the helper:

// Require supertest as request, app, responseStub
var app = require('../index')
[...]

// Activate my stubbing middleware
responseStub.install(app)

// start testing
it("renders the foo page", function(done) {
    request(app).get('/foo')
    .end(function(err, res) {
        expect(res.render).to.have.been.calledWith('foo');  // Sinon-chai assertion
    });
});

The middleware-injection works. When I console.log(res) right before rendering the template it does have a spy as a render method. But the problem is: supertest appears not to hand me THE res object that has been passed through the stack, but a NEW one, as the render method is no longer a spy at assertion time. That's really annoying. I'm sorry if I seem ignorant about this, it's my first testing experience with this setup.

My other option would be to render the handlebars template and check if res.text === rendered, but I don't want to do that.

  • It would add a lot of overhead to all my tests, because for every test I would have to compile, render, check the entire file if they match..
  • It uglifies my test files. Rendering hbs templates server side has some boilerplate to it.
  • I don't want to be testing my views when testing routes. (will have separate tests for those)

I could use the capybara-style approach

expect(res.text).to // include "Welcome to the foo-page!"

But that doesn't seem right to me. When I decide to change to view, I want to only have to change my view tests, not my route tests. That doesn't make any sense.

Something I have done for another controller was to test the controller method alone, and then I could pass in a super stubbed mocked response object and ask anything I want to the little guy. Which was ok, but as I'm adding middleware to the route, I would like to be able to test from start resolve what happens to the response, it seems more valid to check if the middleware is working together properly for any given route. I could just hand of the route directly to the controller method, and let him figure out what middleware he needs, thus being able to test the controller method with a custom stubbed res object. I first wanted to ask for idea's/experience/best practices.

What are your thoughts on this? How do you test this stuff? Is there another library that allows me to stub the res object?

To avoid XY and tldr; → I want to test an entire route that calls res.render() from reception to resolve resolve WITHOUT testing my views.

Aucun commentaire:

Enregistrer un commentaire