4

I'm implementing some testing with Jasmine in my web app. I'm using Coffeescript to write my models, services and view models.

class MyViewModel
  constructor: ( @options ) ->
    @alert = new Alert
      elementId: 'my-alert-element-id'

    @service = new MyService
      alertId: @alert.elementId

Now I write a test in jasmine

describe 'MyViewModel', ->
  sut = null

  beforeEach ->
    sut = new MyViewModel()

  afterEach ->
    sut = null

  describe 'constructor()', ->
    it 'creates a new instance of the ViewModel', ->
      expect( sut ).not.toBeNull()

So the problem here is that I've got dependencies on alert and service in my viewModel. This is making the tests annoying to write and maintain.

Are there libraries out there for depency injection in Javascript. I've used several .net libraries like castle windsor and ninject.

Or should I just be adopting a certain type pattern. I should say that I'm using knockout and when I'm using the viewmodel in my actual application it looks something like this.

<script type="text/javascript">

  $(function () {
    var viewModel = new MyViewModel();
    ko.applyBindings(viewModel);
  });

</script>

Instead of me creating my own object I'd be asking the injection framework for an instance of MyViewModel I assume.

I'm looking for advice on what pattern or library to adopt to make my testing a bit easier and help decoupling my javascript classes from each other.


Libs I've found:

  • Wire - Only ECMA 5+ and I need to support older browsers
  • Dijon - Looks ok but I haven't tried it yet.

EDIT: What I ended up doing

  • I adopted RequireJs and moved all my objects into AMD style modules
  • I use Jamine to run the tests
  • I use Sinon to create mocks and stubs
  • I use Squire to inject mocks and stubs into my system under test

See coffeescript example of a test file

define ['squire', 'sinon' ], ( squire, sinon ) ->
  describe '==== a view model ====', ->
    sut = null
    testContext = null

    beforeEach ->
      testContext =
        squireInjector: new squire
        stubService: sinon.stub()
        stubJquery: sinon.stub()
        someCallbackSpy: sinon.spy()

      testContext.squireInjector.mock( 
        'jquery', squire.Helpers.returns( stubJquery ) )

      testContext.squireInjector.require ['aViewModel'], ( viewModel ) =>
        sut = new viewModel
          service: testContext.stubService
          someCallback: testContext.someCallbackSpy

      waitsFor( -> sut? )

    afterEach ->
      sut = null
      testContext = null

    describe 'the constructor method should', ->
      it 'create a new instance of the view 
        model and have required dependencies', ->
        expect( sut ).toBeDefined
        expect( sut.service ).toBeDefined
        expect( sut.someCallback ).toBeDefined

    describe 'the next method should', ->
      it 'increment the route id by one', ->
        # Arrange
        sut.routeId = 5

        # Act
        sut.next()

        # Assert
        expect( sut.routeId ).toEqual( 6 )
        expect( testContext.someCallbackSpy.called ).toBe(true)
Neil
  • 5,179
  • 8
  • 48
  • 87

2 Answers2

2

There is a library that provides very similar functionality to ninject for Coffeescript, honk-di. Here's a helpful write up about it. Your example would become something more like this:

class MyViewModel
  elementId:  inject('element.id') # Inject a constant
  alert:      inject(Alert)
  service:    inject(MyService)

  constructor: ->
    @alert.elementId = @elementId
    @service.alertId = @alert.elementId

Then, your tests will work just like they would with ninject, Guice, or similar. You describe your testing objects in a module/binder, and simply ask an injector for your class at test time.

describe 'MyViewModel', ->
  sut = null

  beforeEach ->
    # Assuming you've made mocks or simplified classes for
    # Alert and MyService which are set up in TestModule.
    # `element.id` will also need a definition.
    injector = new inject.Injector(new TestModule())
    sut = injector.getInstance(MyViewModel)

  afterEach ->
    sut = null

  describe 'constructor()', ->
    it 'creates a new instance of the ViewModel', ->
      expect( sut ).not.toBeNull()
user1030503
  • 6,642
  • 1
  • 14
  • 5
1

You could use requirejs, and one of this solutions:

Or you just mock the prototypes of your Objects.

jasmine.spy(Alert.prototype, 'someFunction')

Btw. you can use wire.js with older browser using a shim. From the docs:

To support non-ES5 legacy browsers, wire.js 0.9.x requires poly 0.5.0 or higher. You can clone or download poly into your project, or install it via yeoman/bower:

Community
  • 1
  • 1
Andreas Köberle
  • 106,652
  • 57
  • 273
  • 297
  • Would moving to require be a complete rewrite of all my js to work under their framework? – Neil May 29 '13 at 07:12
  • This would be the case with any solution, but as you're using coffeescript classes wich can be simply converted in a requirejs module it shouldn't be that much work. – Andreas Köberle May 29 '13 at 07:24