How to Make a Call to Koa.js App Instance for Unit Tests
Asked Answered
G

1

8

I don't know how I'd term this maybe 'static call to a koa router'? Does that seem like the right wordage here for what I'm really trying to accomplish if you were to talk about it technically?

Anyway, I'm using koa-router and I'm coding unit tests (not integration tests). So I do not want to invoke .listen() on my koa app because of that reason...it would create an http server which now makes my test an integration tests.

Instead in my test I simply want to make a straight call to the app object instance and call a route and be able to return no results and check that I returned no results in the response.

How can you do that? I can't find an example and I've tried all sorts of pseudo code attemps agains the koa app object.

Gretchen answered 16/7, 2015 at 21:22 Comment(10)
testing against the app itself is an integration test. Do you want to test the app itself (integration) or individual middleware (unit tests)?Brookins
NO. I'm testing the Interface (contract) of my API. I am exposing RESTful endpoints through Koa. That means I TDD those endpoints and so my TDD which is NON-Integration..they're unit tests shouldn't be testing over app.Listen(). I'm mearly testing the actual CODE, the SUT which no I'm not testing Koa's framework. You use a framework but you test the domain logic or whatever. My domain logic includes the routes. Because if a developer breaks a route, they just broke a BDD test and broke a business requirement. That's how I develop code.Gretchen
You are getting terminology mixed up here. If you are testing your routes, i.e, testing GET /, then you necessarily have to test that part of your application as a whole - which makes it an integration test. If you are testing individual functions (which you are not), you are unit testing. You don't want to test the layer that connects your application with koa (which should be very thin, btw), you only want to integration test that bit.Brookins
"Create and return an HTTP server" - straight from the koa docs. For app.listen().Gretchen
I don't test all individual functions, TDD'ers have found you can over test this route. Test the boundaries with bout unit tests AND integration tests and test drive from your boundaries (your units..where a unit could be a service contract for example) that pushes you to TDD the code on down. Times have changed, people are no longer doing traditional TDD where your'e creating a crapload of tests for every function. Tests get too coupled and you end up with a tone of broken tests, you only need to test boundary units and units of code.Gretchen
OK first of all - you don't use koa.listen for the millionth time. I also employ tdd.Brookins
You must use listen(). I couldn't get it to work UNTIL I added .listen() to the end of my definition. code.tutsplus.com/tutorials/…Gretchen
No you don't. Here, I just wrote this test an hour ago. pastebin.com/v11Tp1spBrookins
thanks Dan! How did you do the faker? appreciate the example. I must have been doing something wrong because for me it wasn't working until I added the .listen() so I'll revisit my code thenGretchen
Faker just generates random contextual data, it's not important for the example I gaveBrookins
D
5

If you want to test the function that koa-router routes to then just perform a unit test on that function and leave the routing out of it.

To me it sounds like you've got a file such as app.js and it contains all your code. What you can do is create a router.js file to put you route bindings and a services.js file where you can put your application logic.

So for example app.js might look like:

var koa = require("koa");
var app = module.exports = koa();
var router = require('./router.js');

app.use(router.unsecured.middleware());

app.listen(3000);

And router.js might look like:

var router = require("koa-router");
var service = require("./services.js");

var unsecured = module.exports.unsecured = new router();

unsecured.post('/account/signin', service.signinUser);
unsecured.post('/account/register', service.registerUser);

And services.js might look like:

module.exports.signinUser  = function*(signinDetails) {
  // contains your application signin logic
};

module.exports.registerUser  = function*(registerDetails) {
  // contains your application register logic
};

So in this manner you can individually test services.js. I don't see any value in individually testing router.js since it is so trivial. As @Dan Pantry shows you can test routing as part of an integration test using supertest.

Edit:

So this is a little experimental test I was playing around with to test that the routing is correct. I'm using mocha as the test runner and the code example I posted in my original code.

// standard library
var assert = require("assert");

// in app objects
var router = require('./router.js');
var service = require('./service.js');

describe("routing tests", function() {

  it("test register routing, POST", function*(done) {
    // arrange
    var unsecured = router.unsecured;
    var path = '/account/register';
    var httpMethod = 'POST';
    var expected = service.register.toString();
    var actual;

    // act
    for (var i = 0; i < unsecured.stack.length; i++)
    {
      var pathMatch = unsecured.stack[i].path === path;
      var methodMatch = unsecured.stack[i].methods.indexOf(httpMethod) >= 0;

      if (pathMatch && methodMatch)
      {
        actual = unsecured.stack[i].middleware.toString();
        break;
      }
    }

    // assert
    try {
      assert.equal(expected, actual);
      done();
    } catch(err) {
      done(err);
    }
  });    
});

There is probably a neater way of doing this (and a more modular way for testing multiple paths) but as I said this is just a basic example to verify the routing is calling the correct service. What I'm doing is delving into the koa-router object to verify what path is bound to what service code depending on the HTTP method (e.g. POST, GET, etc).

If you have your routing and your services in modules this test completely avoids dealing with the main koa app. Although technically this test spans multiple units (the routing and the service code) so it would technically be an integration test but it does mean you don't go near app.listen() which is what you didn't want to call in your tests.

Dewaynedewberry answered 17/7, 2015 at 12:5 Comment(7)
yes this is how I have it segregated already...but you have your app.listen in app.js so that is a huge difference, good stuff.Gretchen
the reason for testing the router is because some dumb programmer could f it up. They could remove a route, they could mess with the js and we end up with a null router, all sorts of things so I think testing that unit is important.Gretchen
Check my edit for a potential approach to avoiding what you're worried about but also avoiding the supertest approach.Dewaynedewberry
It won't work without app.listen which would make my tests integration tests. I don't want an instance of an http server, I wanna test against the koa middleware itself and make calls straight up to the app instance methods.Gretchen
where's the unsecured value coming from node.js?Gretchen
and what's the register coming from on service?Gretchen
do you have a test to show how you tested your first example? What I My goal is to test my controller actions. Really wanna do is make a request to my koa route, grab the context from my controller action that the route is mapped to (I'm using koa-controller so I get the context via "this" once the request hits my node controller module) and then I can pass in my request and assert some things about my controller actionGretchen

© 2022 - 2024 — McMap. All rights reserved.