Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jasmine shared specs scoping issues with coffeescript

I'm attempting to DRY up some jasmine tests by extracting out shared examples.

@sharedExamplesForThing = (thing) ->
  beforeEach ->
    @thingy = new thing

  it "is neat", ->
    expect(@thingy.neat).toBeTruthy()


describe "widget with shared behavior", ->
  sharedExamplesForThing(-> new Widget)

This works nicely when everything is defined in one file. The problems I'm encountering occur when I try to move the sharedExamples to a separate file. I get Can't find variable: sharedExamplesForThing ...

So in the interest of debugging, I tried the following:

describe "widget with shared behavior", ->
  it "is acting like a meany", ->
    console.log sharedExamplesForThing
    expect(false).toBeTruthy()
  sharedExamplesForThing(-> new Widget)

In the is acting like a meany block, the log shows sharedExamplesForThing as [Function] but I still get the Can't find variable outside the it. I feel like this might have something to do with a scoping issue outside of my current experience, but I could be completely wrong about that. What am I missing here?

(using rails, jasminerice, guard-jasmine)

like image 694
Lee Quarella Avatar asked Mar 01 '13 16:03

Lee Quarella


2 Answers

I found the piece on shared examples from thoughtbot really useful.

I implemented it in coffeescript as follows:

1) In some helper file that is loaded before all spec files:

window.FooSpec =
  sharedExamples: {}

window.itShouldBehaveLike = (->
  exampleName      = _.first(arguments)
  exampleArguments =
    _.select(_.rest(arguments), ((arg) => return !_.isFunction(arg)))
  innerBlock       = _.detect(arguments, ((arg) => return _.isFunction(arg)))
  exampleGroup     = FooSpec.sharedExamples[exampleName]

  if(exampleGroup)
    return describe(exampleName, (->
      exampleGroup.apply(this, exampleArguments)
      if(innerBlock) then innerBlock()
      )
    )
  else
    return it("cannot find shared behavior: '" + exampleName + "'", (->
      expect(false).toEqual(true)
      )
    )
)

2) For my specs:

(a) I can define a shared behaviour:

FooSpec.sharedExamples["had a good day"] = (->
  it "finds good code examples", ->
      expect(this.thoughtbot_spy).toHaveBeenCalled()
)

(b) And use it anywhere in some spec as:

itShouldBehaveLike("had a good day")

(Note: I am assuming the spec has defined this.thoughtbot_spy accordingly before the above line)

like image 142
Sunil Sandhu Avatar answered Nov 03 '22 00:11

Sunil Sandhu


When you assign a top-level member variable in CoffeeScript it is assigned as a property of the global object (window in a Browser). So it generates the following JavaScript:

window.sharedExamplesForThing = ...;

That means that you can refer to it outside the file as window.sharedExamplesForThing or just sharedExamplesForThing. So what you are doing should work assuming the shared example file has been loaded before the spec file. I think the problem you have having is that the spec file is loaded first (because describe functions are run as the file is loaded whereas it functions are run after all the files have loaded). So you might need to adjust the load order, you could try putting your shared examples files in a support directory and then requiring this first.

Rather than assigning variables directly to the window object it may be better to set up a namespace to export your shared variables into (so that you don't clutter the global object):

window.MyNamespace = {}

MyNamespace.sharedExamplesForThing = ...

Then in your spec file you can refer to it as MyNamespace.sharedExamplesForThing.

I find it helpful to look at the generated JavaScript to try to understand how CoffeeScript exports variables between files.

like image 27
Steve Avatar answered Nov 02 '22 22:11

Steve