0

I am using the following construct as a Mock object which functions as an object with chainable methods:

PlayerNull =
  find : ->
    populate : ->
      exec : (callback) ->
        callback false, false

In my tests I then substitute the real model for this Mock object and my controller calls each of the functions in turn such as:

Model.find().populate().exec(callback)

As I'm finding myself using this many times, I was curious as to whether I could create a helper function to simplify this (slightly), using a helper function in the following form:

PlayerNull = helper.mockNest ['find', 'populate', 'exec'], (callback) ->
  callback false, false

I've come to the following function code, however this is not working:

exports.mockNest = (func_names, func_final) ->
  func_names.reverse()
  func_next = func_final
  for func_name in func_names
    _func_next = func_next.bind({})
    _next = {}
    _next[func_name] = ->
      _func_next
    func_next = _func_next
  func_next

I've come to the realization that I need to clone func_next each loop or else the reference seems to be maintained and assigning to func_next just seems to alter all previous assignments.

2 Answers 2

2

Yes, you've identified the problem correctly that the reference is not maintained, it's the classical closure in a loop problem.

However, cloning the function doesn't really help here, your actual problem is

_next[func_name] = ->
  _func_next

which creates a closure over the _func_next variable, which is modified in each loop iteration.

It should rather be

exports.mockNest = (func_names, func_final) ->
  func_names.reverse()
  func_next = func_final
  for func_name in func_names
    next = {}
    next[func_name] = func_next
    func_next = do (_next = next) ->
      () ->
        _next
  func_next
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks very much for explaining the problem and bringing my attention to the do keyword, very cool, and got me over the finish line. Your final code didn't work exactly, I'll add an answer and accept this.
Oops, you're right it didn't work; but the fix is rather small. In contrast to your code, it even works with an empty func_names array now.
0

Thanks to Bergi's explanation, I came to the following solution:

exports.mockNest = (func_names, func_final) ->

  # Assign the last function to the provided func_final
  func_name_final = func_names.pop()
  func_next = {}
  func_next[func_name_final] = func_final

  # Reverse the array so we build the object from last to first.
  func_names.reverse()
  for func_name in func_names
    ignore = do (func_name, _func_next = func_next) ->
      func_next = {}
      func_next[func_name] = ->
        _func_next
      return
  func_next

To explain the changes, I needed to preserve the supplied func_final, so I had to assign before the loop.

Also, I needed to assign the do block to variable ignore or else Coffeescript compiled the function to different location where changes made to func_next were made out-of-order. Assigning ensures the placement of the do block.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.