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