Unit-Testing Collaborating Objects [team]

posted in: AngularJS, Standards, Testing | 0

Testing is just like learning how to program again, so there’s always something to be learned. And when it comes to learning how test and program better and better it’s just like the cleaning of a house … It never ends 🙂  http://projectpoppycock.com/mocking-resource-and-promises-in-angularjs-unit-tests/

 

In “Growing Object-Oriented Software, Guided by Tests” we read:

We appear to have painted ourselves into a corner. We’re insisting on focused objects that send commands to each other and don’t expose any way to query their state, so it looks like we have nothing available to assert in a unit test.
One option is to replace the target object’s neighbours in a test with substitutes, or mock objects. We can specify how we expect the target object to communicate with its mock neighbors for a triggering event; we call these specifications expectations. During the test, the mock objects assert that they have been called as expected; they also implement any stubbed behavior needed to make the rest of the test work. (p.18)

Screen Shot 2014-10-23 at 4.27.43 PM

How do you do this in AngularJS and Karma?

 

Please ignore the obvious lack of getter and setter pattern – this is an example from a test of everything that works, not how to design well.

Let’s take the Pet project example.

We have 2 core objects which are both not aware of the framework context (except that we use the Angular dependency injection feature to access them).

So we want to look at only one property accessed from within PetModel and it is otherProp.

At runtime it will access OtherModel.publicOtherProp, but when we test PetModel in isolation we don’t want it to reach into OtherModel.publicOtherProp at all.

We want to isolate PetModel and mock as much of OtherModel as we need to to pass the assertions we are making in our tests.

Here is how to create a mocked OtherModel and give it a publicOtherProp with a test value ‘mocked’.

 

beforeEach(ng.mock.module(
  function ($provide) {
    $provide.value('OtherModel', {
      publicOtherProp: 'mocked'
    });
   }
));

 

Here is the first:

 

// pet_model.js

define(['angular', '../module'], function (ng) {
    'use strict';

    ng.module('pet.models')
        .service('PetModel', [

            // add other Service name here to inject "subModule,"
            'OtherModel',

            function (OtherModel) {

                var accumilator = 0;

                var privatePetProp = 'petModel-privatePetProp';

                function privatePropFunction(){
                    return privatePetProp;
                }

                function privateFunction(){
                    return 'petModel-privatePetFunc';
                }

                return {

                    domainAccumilator: accumilator,

                    petProp: 'petModel-petProp',

                    otherProp: OtherModel.publicOtherProp,

                    publicPetProp: privatePropFunction(),

                    petFunc: function () {
                        return 'petModel-petFunc';
                    },

                    publicPetFunc: function(){
                        return privateFunction();
                    },

                    addSomething: function () {
                        this.domainAccumilator = this.domainAccumilator + 1;
                        return this.domainAccumilator;
                    }

                };

            }

        ]);

});

 

And here is the second:

 

// other_model.js

define(['angular', '../module'], function (ng) {
    'use strict';

    ng.module('pet.models')
        .service('OtherModel', [

            // add other Service name here to inject "subModule,"
            function () {

                var privateOtherProp = 'otherModel-privateOtherProp';

                function privatePropFunction(){
                    return privateOtherProp;
                }

                return {

                    publicOtherProp: privatePropFunction()

                };

            }

        ]);

});

 

Here are the tests:

 

// petmodule.unit.test.js

define(['angular', 'angular-mocks'], function (ng) {
  'use strict';

  describe('Model: PetModel', function () {

    var model;

    beforeEach(ng.mock.module('app'));

    beforeEach(ng.mock.module(
      function ($provide) {
        $provide.value('OtherModel', {
          publicOtherProp: 'mocked'
        });
      }
    ));

    beforeEach(ng.mock.inject(function (PetModel) {
      model = PetModel;
    }));

    it('should be able to access PetModel.petProp', function () {
      model.petProp.should.equal('petModel-petProp');
    });

    it('should be able to access PetModel.publicPetProp', function () {
      model.publicPetProp.should.equal('petModel-privatePetProp');
    });

    it('should be able to access PetModel.petFunc()', function () {
      model.petFunc().should.equal('petModel-petFunc');
    });

    it('should be able to access PetModel.publicPetFunc()', function () {
      model.publicPetFunc().should.equal('petModel-privatePetFunc');
    });

    it('should access PetModel.addSomething()', function () {
      model.addSomething();
      model.addSomething();
      model.addSomething();
      model.domainAccumilator.should.equal(3);
    });

    it('should be able to access PetModel.otherProp', function () {
      // before we mocked OtherModel this test passed
      //model.otherProp.should.equal('otherModel-privateOtherProp');
      model.otherProp.should.equal('mocked');
    });

  });

  describe('Model: OtherModel', function () {

    var othermodel;

    beforeEach(ng.mock.module('app'));

    beforeEach(ng.mock.inject(function (OtherModel) {
      othermodel = OtherModel;
    }));

    it('should be able to access OtherModel.otherProp', function () {
      othermodel.publicOtherProp.should.equal('otherModel-privateOtherProp');
    });

  });

});

 

    it('should be able to access PetModel.otherProp', function () {
      // before we mocked OtherModel this test passed
      //model.otherProp.should.equal('otherModel-privateOtherProp');
      model.otherProp.should.equal('mocked');
   });

Leave a Reply