Neil
Neil

Reputation: 5239

Dependency Injection with javascript / coffeescript to aid testability

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:


EDIT: What I ended up doing

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)

Upvotes: 4

Views: 1466

Answers (2)

user1030503
user1030503

Reputation: 6972

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()

Upvotes: 2

Andreas K&#246;berle
Andreas K&#246;berle

Reputation: 110972

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:

Upvotes: 1

Related Questions