Question

As a learning exercise, I'm trying to get a version of the Bowling Game kata running using Node and Express, and I'm seeing a peculiar issue. I could use some help understanding why from someone who knows Node and Express better than I do.

After writing the strike test case while trying to get the strike test case working, when I try to run the below tests using mocha at the command line, I get the following supertest error: "Uncaught TypeError: undefined is not a function" at /Users/cdurfee/bitbucket/neontapir/node_modules/supertest/lib/test.js:125:21.

However, if I comment out this seemingly innocuous line in game.js (total += rolls[ball + 2];), there are no failures, but of course the behavior is wrong. I suspect it's an array out of bounds issue, but I don't know a good way to figure that out.

Here's the full contents of both files and the console output of mocha.

08:55 $ mocha --reporter spec

  Scoring a bowling game
    gutter game
      ✓ should return 0 
    single pin game
      ✓ should return 20 
    spare
      ✓ should return 16 after spare and a 3 
    strike
      ✓ should return 24 after strike, 4 and a 3 
      1) should return 24 after strike, 4 and a 3
double callback!
double callback!
double callback!
double callback!
double callback!
double callback!
double callback!
double callback!
double callback!
double callback!
double callback!
double callback!
double callback!
double callback!
double callback!
    perfect game

  4 passing (71ms)
  1 failing

  1) Scoring a bowling game strike should return 24 after strike, 4 and a 3:
     Uncaught TypeError: undefined is not a function
      at /Users/cdurfee/bitbucket/neontapir/node_modules/supertest/lib/test.js:125:21
      at Test.Request.callback (/Users/cdurfee/bitbucket/neontapir/node_modules/supertest/node_modules/superagent/lib/node/index.js:660:30)
      at ClientRequest.<anonymous> (/Users/cdurfee/bitbucket/neontapir/node_modules/supertest/node_modules/superagent/lib/node/index.js:628:10)
      at ClientRequest.EventEmitter.emit (events.js:95:17)
      at Socket.socketErrorListener (http.js:1547:9)
      at Socket.EventEmitter.emit (events.js:95:17)
      at net.js:441:14
      at process._tickCallback (node.js:415:13)

game.js

var express = require('express');
var app = exports.app = express();

app.get('/start', function(req, res) {
    rolls = new Array();
    attempt = 0;
});

app.post('/bowl/:pins', function(req, res) {
    rolls[attempt] = parseInt(req.params.pins);
    attempt++;
});

app.get('/score', function(req, res) {
    var total = 0;
    var ball = 0;
    for (var frame = 0; frame < 10; frame++) {
        if (rolls[ball] + rolls[ball + 1] == 10) { 
            total += rolls[ball + 2]; // this line causes the double callback
        }
        total += rolls[ball] + rolls[ball + 1];
        ball += 2;
    }

    res.send(200, {score: total});
});

app.listen(process.env.PORT || 3000);

test/test.js

var request = require('supertest'),
    should = require('should');
var game = require('../game.js').app;

var assertScoreEquals = function(expectedScore) {   
    request(game).get('/score').expect(200).end(function(err,res) {
      should.not.exist(err);        
      result = res.body;        
      result.should.have.property('score').eql(expectedScore);      
    });  
};

var roll = function(pins) {
    request(game).post('/bowl/' + pins).end();
};

var rollMany = function(times, pins) {
    for (var i = 0; i < times; i++) {
        roll(pins);
    }
};

describe('Scoring a bowling game', function() {
  beforeEach(function() {
    request(game).get('/start').end();
  });

  describe('gutter game', function() {
        it('should return 0', function() {
            rollMany(20,0);
            assertScoreEquals(0);                       
        });
    });

    describe('single pin game', function() {
        it('should return 20', function() {
            rollMany(20,1);
            assertScoreEquals(20);          
        });
    });

    describe('spare', function() {
        it('should return 16 after spare and a 3', function() {         
            roll(6);
            roll(4); // spare
            roll(3);
            rollMany(17,0);
            assertScoreEquals(16);          
        });
    });

    // not expected to pass at the moment
    describe('strike', function() {
        it('should return 24 after strike, 4 and a 3', function() {         
            roll(10); // strike
            roll(4);
            roll(3);
            rollMany(17,0);
            assertScoreEquals(24);          
        });
    });

    // not expected to pass at the moment
    describe('perfect game', function() {
        it('should return 300', function() {    
            rollMany(12,10);
            assertScoreEquals(300);         
        });
    });         
});
Was it helpful?

Solution

Adding to my previous comment

I also see that your test cases are not asynchronous, this is almost certainly your problem.

You should have a callback: done

  describe('gutter game', function() {
    it('should return 0', function(done) { // callback is provided as argument by mocha 
        rollMany(20,0);
        assertScoreEquals(0);
        done();  // This needs to be called when the test is finished, for async operations.
    });
});

I would suggest perhaps using supertest-as-promised instead of supertest, as this can make things easier when you need to run a lot of requests. This library, plus perhaps bluebird can make tests like yours simpler.

This gist has my rewrite of your tests, using Bluebird promises and supertest-as-promised, with comments on what I've changed.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top