After modifying attributes on a node.js vm context (sandbox), the modifications aren't seen by code in the context

Background

I'm working on tests for the Backbone.dualStorage plugin for Backbone.js that replaces Backbone.sync.

This is a browser plugin, but I'm using jasmine-node to test it. I load the coffeescript source into the node environment to simulate the browser window using vm.createContext:

vm = require 'vm'
fs = require 'fs'
coffee = require 'coffee-script'
backboneDualstoragePath = './backbone.dualstorage.coffee'
source = fs.readFileSync(backboneDualstoragePath, 'utf8')
window = require('./global_test_context.coffee').window
context = vm.createContext window
coffee.eval source, sandbox: context, filename: backboneDualstoragePath
exports.window = context

This works great - in my tests, I can access window.foo, where foo is an attribute exported on the window object in global_test_context.coffee.

I can use Jasmine spies on and redefine methods on any object nested under the window sandbox. Eg:

spyOn(window.Store.prototype, 'create').andReturn(true)
window.Store.create() # The spy is called
window.localsync.create() # This function instantiates a Store object and calls create on it
                          # The spy is called

However if I try to spy on or otherwise modify the context's direct attributes, the change is not seen inside the sandbox context:

spyOn(window, 'localsync').andReturn(true)
window.localsync() # The spy is called
window.dualsync() # This function references the global localsync
                  # The original localsync is called

The vm.createContext documentation says:

A (V8) context comprises a global object together with a set of build-in objects and functions. The optional argument initSandbox will be shallow-copied to seed the initial contents of the global object used by the context.

So it sounds like it copies the attributes from my window variable into the vm context. When I spy on or otherwise modify window after this point, I am working with the vm context, which I export as an attribute named window. Therefore I think that the paragraph above is irrelevant, but I wanted to include it in case I'm wrong.

The Question

The flow of events boils down to this:

window = vm.createContext({globalVariables: forTesting...})

# similiar to vm.runInContext(backboneDualstorageSource, window)
coffee.eval(backboneDualstorageSource, sandbox: window)

# localsync and dualsync are both defined in the backboneDualstorageSource
spyOn(window, 'localsync')
window.dualsync() # calls the original localsync instead of the spy

Why is it that after modifying attributes on the vm context, references to those "global" attributes/functions inside the vm don't change? I want to understand this.

How can I work around this so that I can modify/spyOn globals in the browser script I'm testing?

Feel free to look at the source to get a better idea of how things are actually written compared to the snippets in this question.

Edit

I was able to work around this issue by creating the spy inside an eval that runs in the context, like the rest of the tested code. See https://github.com/nilbus/Backbone.dualStorage/commit/eb6c2b21

Could someone explain why I'm able to modify global variables within the context but not outside the context, even though I have access to them?

Modifying context after eval is not affecting already evaluated scripts, if I understand your issue correctly. If you don't need to add new members to context, and just need to modify existing ones, use getters, which is working fine in latest node (0.8.x for now).

don't go down one level with the global key, try this:

window = vm.createContext({variablesForTesting.. })