Hacking State in Vows.js with CoffeeScript

I can’t tell if you’re meant to do this kind of thing to vows.  I honestly can’t.  It’s either a demonstration of the power of the architecture, or it’s a phenomenal hack that shouldn’t be allowed out in broad daylight.  Either way, I’m probably thinking too LISP-ily for my own good.

Let’s say that you’re trying to test the behaviour of a workflow.  Under certain conditions, certain things should happen.  The problem is, some of those certain conditions are pretty verbose.  In fact, if you’ve got three yes/no decisions to make, you’re left having to set up eight different scenarios (more if you’re testing the intermediate states).

Now, in most testing environments, this is what you have “setup” for.  However, it only really works if you can have nested setup procedures, like “before” in RSpec.  Vows, on the other hand, creates a topic once and then tests run against that one topic.  Not really ideal for testing workflows.  So, I thought “why not make the topic itself a factory”.  That way, I could call the topic in each test and set it up repeatedly.  Then it occurred to me that, ideally, later topics should contain instructions on how to get the topic into the correct state, reducing the amount of repetition that we saw in my previous post.  Finally it occurred to me that, ultimately, the entire batch specification is just a hash table.

So, I wrote a function that rewrites a batch to do workflows:

withSetup = (batch) ->
  setupTopic = (f) -> (topic) -> ->
    # N.B. The topic is a factory.  The setupTopic function returns a factory as well
    return f() unless topic? # Resolve item
    # Apply item to topic and return topic
    t = topic()
    f(t)
    t
  inner = (item) ->
    # The item is a test
    # Take the topic, resolve it and run tests in "item"
    return ((topic) -> item topic()) unless typeof item == 'object'
    # The item is a batch
    for k,v of item
      item[k] = (if k == 'topic' then setupTopic else inner) v
    item
  inner batch

All you need to do is add withSetup to the addBatch invocation.

vows.describe('Guessing Game').addBatch(withSetup({
  'Player is playing a guessing game' : {
    topic : -> new game.Player(new StubEmitter(), guessingGameFactory)
    'should be able to start a game' : (p) ->
      assert.isFalse p.game?
      p.client.emit 'message', { action : 'start' }
      assert.isNotNull p.game
      assert.equal p.client.data.question, "Guess what number I'm thinking of"
    'after game has started' : {
      topic : (p) -> p.client.emit 'message', { action : 'start' }
      'correct guess' : (p) ->
        p.client.emit 'message', { action : 'answer', answer : 1 }
        assert.isTrue p.client.data.wasRight
      'wrong guess' : (p) ->
        p.client.emit 'message', { action : 'answer', answer : 2 }
        assert.isFalse p.client.data.wasRight
      'after correct answer' : {
        topic : (p) -> p.client.emit 'message', { action : 'answer', answer : 1 }
        'We're now on the second question' : (p) ->
          assert.equal p.playerActions.game.currentQuestionCount, 2
      }
    }
  }
})).export module

As I say, I can’t figure out if this works because Cloudhead’s really smart or I’m really stupid.

Published by

Julian Birch

Full time dad, does a bit of coding on the side.

One thought on “Hacking State in Vows.js with CoffeeScript”

  1. Have you developed this ideas further?If I understand what you are saying here, this is how I’m actually using the Rspec let() feature — for declaring little snippets that end up with a factory. I’m not very happy with how I use it though. What I really want is end up with some sort of auto-discovery testing.

    Like

Leave a comment