2

I am running into some timing issues with some test scripts that run against services. I'm using the Restify JSON client to issue the calls, which use callbacks rather than promises. I've passed in the done function to my Givens and Whens, so that I can perform the necessary blocking against these async calls, which prevents inconsistent test suite runs (without the dones, it's a tossup which and how many Thens and Ands will pass).

I am moderately skilled with coffescript, and only a novice when it comes to mocha/mocha-cakes, so I am most certainly doing something wrong in my code. Here is an example of a couple of the test cases that are failing:

require 'mocha-cakes'
should = require 'should'
restify = require 'restify'


Feature "Account API",
  "In order to have control over structured Account documents",
  "as a consumer of investment account information,",
  "I need a RESTful service API.", ->

    Scenario "GET /account/:userid", ->

      client = restify.createJSONClient
        url: "http://localhost:8080",
        version: "*"

      _e1 = null
      _r1 = null

      _e2 = null
      _r2 = null
      _d2 = null

      # GET non-existent account
      Given "I have not yet created the Account", ->
      When "I request the Account", (done) ->
        client.get "/account/99", (err, req, res, obj) ->
          _e1 = err
          _r1 = res
          done()
          err

      Then "it should respond with an error", ->
        _e1.should.be.ok
      And "the status code should be 404", ->
        _r1.should.have.status 404


      # GET existent account
      Given "I have created the Account", (done) ->
        client.post "/account", { userId: 1, accountType: 0, accountCategories: [], beneficiaries: [], accountOwner: { firstName: "Test", lastName: "User" } }, (err) ->
          done()
          err

      When "I request the Account", (done) ->
        client.get "/account/1", (err, req, res, obj) ->
          _e2 = err
          _r2 = res
          _d2 = obj
          done()
          err

      Then "it should responond with a document", ->
        _d2.should.be.ok
      And "it should have the userId 1", ->
        _d2.userId.should.eql 1
      And "it should have an accountOwner property", ->
        _d2.accountOwner.should.be.ok
      And "the status code should be 200", ->
        _r2.should.have.status 200

When I run this, my output is always the following:

c:\Development\Clients\Pensco\AngularJS\Pensco\newaccountwizard.api>mocha test/AccountAPITests.coffee -r should -R spec --compilers coffee:coffee-script/register

Feature: Account API

    In order to have control over structured Account documents
    as a consumer of investment account information,
    I need a RESTful service API.


Scenario: GET /account/:userid
  ◦
    - ◊ Given: I have not yet created the Account (pending)
  ◦
    1)  When: I request the Account
  ◦
    √  Then: it should respond with an error
  ◦
    √   And: the status code should be 404
  ◦
    2) Given: I have created the Account
  ◦
    3)  When: I request the Account
  ◦
    √  Then: it should responond with a document
  ◦
    √   And: it should have the userId 1
  ◦
    √   And: it should have an accountOwner property
  ◦
    √   And: the status code should be 200

6 passing (6s) 1 pending 3 failing

1) Feature: Account API

    In order to have control over structured Account documents
    as a consumer of investment account information,
    I need a RESTful service API.

Scenario: GET /account/:userid ◦  When: I request the Account:
 Error: timeout of 2000ms exceeded
at [object Object].<anonymous> (C:\Users\Jon\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:139:19)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)

2) Feature: Account API

    In order to have control over structured Account documents
    as a consumer of investment account information,
    I need a RESTful service API.

Scenario: GET /account/:userid ◦ Given: I have created the Account:
 Error: timeout of 2000ms exceeded
at [object Object].<anonymous> (C:\Users\Jon\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:139:19)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)

3) Feature: Account API

    In order to have control over structured Account documents
    as a consumer of investment account information,
    I need a RESTful service API.

Scenario: GET /account/:userid ◦  When: I request the Account:
 Error: timeout of 2000ms exceeded
at [object Object].<anonymous> (C:\Users\Jon\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:139:19)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)

Now, I know that my REST calls via client.get/client.post occur almost instantaneously. When I remove the dones, and run without them, with the exception of the first run after restarting my restify service server, usually only the first or second Then/And fails, the rest succeed. There is a timing issue of maybe a few milliseconds, but definitely not 2000ms. I'm curious why my Givens and Whens suddenly start timing out when I throw in the done() calls.

I am pretty sure I am misunderstanding how mocha-cakes is transforming the coffescript Feature->Scenario->Given/When->Then/And/... into describe/it calls. I suspect that somehow the scope within which done applies is larger than it would seem to be given the nature of mocha-cakes script structure...I'm just not sure exactly what that scope is.

Emissary
  • 9,954
  • 8
  • 54
  • 65
jrista
  • 32,447
  • 15
  • 90
  • 130

1 Answers1

0

I am also not familiar with mocha-cakes. I am using mocha/(lit)coffee to test restify. I have found it convenient to wrap my calls in promises, as latest mocha is promise-aware. Then I don't have to bother with "done". Also note you may need to call res.end() or res.resume() (See this explanation)

For just "GET":

Promise = require('bluebird')  # or whatever, I presume
port = 8776                    # ditto

getHttpJson = (addr)->
  addr = normalizeUrl(addr)
  new Promise ( resolve, reject )->
    req = http.get(addr, _completeResponse(resolve) )
      .on( 'error', reject )
    req.end()

General case:

requestHttpJson = (method, addr, data)->
  if data?
    data = JSON.stringify(data)
  urlBits = getUrlBits(addr)
  new Promise (resolve, reject)->
    req = http.request(
      method: method
      headers: {
        "Content-Type": "application/json" }
      hostname: urlBits.hostname
      port: urlBits.port
      path: urlBits.pathname
    , _completeResponse(resolve) )
    req.on( 'error', reject )
    if data?
      req.write( data )
    req.end()

postHttpJson = (addr, data)->
  requestHttpJson('POST', addr, data)
putHttpJson = (addr, data)->
  requestHttpJson('PUT', addr, data)
deleteHttpJson = (addr, data)->
  requestHttpJson('DELETE', addr, data)

Break down address into components and add defaults. ("port" is a module global.)

getUrlBits = (addr)->
  bits = url.parse(addr)
  bits.port = bits.port || port
  bits.hostname = bits.hostname || 'localhost'
  bits.protocol = bits.protocol || 'http'
  return bits

normalizeUrl = (addr)->
  url.format(getUrlBits(addr))

Utility to parse body of request & resolve.

_completeResponse = (resolve)->
  (res)->
    body = []
    res.on 'data', (data)->
      body.push data
    res.on 'end', ->
      body = body.join ''
      content = if body == '' then null else JSON.parse(body)
      resolve([res,content])
Community
  • 1
  • 1
shaunc
  • 5,317
  • 4
  • 43
  • 58