Patrick McElhaney
Patrick McElhaney

Reputation: 59271

How can I test a Backbone subclass in Mocha without duplicating tests for the superclass?

Say I have a couple of Backbone models, Rectangle and Triangle, that each extend Polygon.

var Polygon = Backbone.Model.extend({
    defaults: {
       width: 100,
       height: 100,
       rotation: 0
    },
    rotate: function (degrees) {
       var oldRotation = this.get('rotation');
       var newRotation = (oldRotation + degrees) % 360;
       this.set('rotation', newRotation);
       return newRotation;
    }
});

var Rectangle = Polygon.extend({
    area: function (degrees) {
      return this.get('width') * this.get('height');
    }
});

var Triangle = Polygon.extend({
    area: function (degrees) {
      return this.get('width') * this.get('height') / 2;
    }
}

I want to test Rectangle and Triangle and ensure that each of them independently implements rotate correctly, even though I know that (for now) they both inherit the implementation of rotate from Polygon.

What I don't want to do is create separate unit tests for Rectangle and Triangle that are almost exact duplicates of one another. In Mocha, how can I write a test for rotate and reuse it in the unit tests for both Triangle and Rectangle?

Here are my unit tests. Note the duplicate 'rotates by 45 degrees' test.

describe('A rectangle', function () {
  var r = new Rectangle({width: 50, height: 10, rotation: 90});
  it('correctly calculates its area', function () {
    expect(r.area()).to.equal(500);
  });
  it('rotates by 45 degrees', function () {
    expect(r.rotate(45)).to.equal(135);
  });
});

describe('A triangle', function () {
  var t = new Triangle({width: 50, height: 10, rotation: 90});
  it('correctly calculates its area', function () {
    expect(t.area()).to.equal(250);
  });
  it('rotates by 45 degrees', function () {
    expect(t.rotate(45)).to.equal(135);
  });
});

Upvotes: 4

Views: 252

Answers (2)

Ivan Drinchev
Ivan Drinchev

Reputation: 19581

What I usually do is :

describe('A rectangle', function () {
    var r = new Rectangle({width: 50, height: 10, rotation: 90});

    it('should be a polygon', function() {
      expect(r).to.be.an.instanceof(Polygon);
    });

    it('correctly calculates its area', function () {
      expect(r.area()).to.equal(500);
    });
});

describe('A triangle', function () {
    var t = new Triangle({width: 50, height: 10, rotation: 90});               

    it('should be a polygon', function() {
      expect(r).to.be.an.instanceof(Polygon);
    });

    it('correctly calculates its area', function () {
      expect(t.area()).to.equal(250);
    });
});

describe('A polygon', function() { 
    var p = new Polygon({width: 50, height: 10, rotation: 90});
    it('rotates by 45 degrees', function () {
      expect(p.rotate(45)).to.equal(135);
    });
});

If you use proper OOP practices, your Polygon class should have it's methods and structure safe enough to rely that any class that extends it should not be able to modify it.

Upvotes: 0

Patrick McElhaney
Patrick McElhaney

Reputation: 59271

The answer is obvious after writing it out. We can create a function to encapsulate the tests for a polygon.

var describeAPolygon = function (name, p) {
    describe(name, function () {        
      it('rotates by 45 degrees', function () {
        expect(p.rotate(45)).to.equal(135);
      });
    });
};

And then use it in the tests for Rectangle and Triangle.

describe('A rectangle', function () {
    var r = new Rectangle({width: 50, height: 10, rotation: 90});
    describeAPolygon('A rectangle', r);
    it('correctly calculates its area', function () {
      expect(r.area()).to.equal(500);
    });
});


describe('A triangle', function () {
    var t = new Triangle({width: 50, height: 10, rotation: 90});
    describeAPolygon('A triangle', t);
    it('correctly calculates its area', function () {
      expect(t.area()).to.equal(250);
    });
});

That's the cleanest solution I've been able to find. I'm curious if anyone else has tackled this problem and come up with something different.

Upvotes: 1

Related Questions