[ACCEPTED]-What's a good way to reuse test code using Jasmine?-jasmine

Accepted answer
Score: 31

Here is an article by a guy at Pivotal Labs 3 that goes into detail about how to wrap 2 a describe call:

DRYing up Jasmine Specs with Shared Behavior

Snippet from the article 1 that shows part of the wrapper function:

function sharedBehaviorForGameOf(context) {
  describe("(shared)", function() {
    var ball, game;
    beforeEach(function() {
      ball = context.ball;
      game = context.game;
    });
  });
}
Score: 17

I'm not sure how @starmer's solution works. As 7 I mentioned in the comment, when I use his 6 code, context is always undefined.

Instead what 5 you have to do (as mentioned by @moefinley) is 4 to pass in a reference to a constructor 3 function instead. I've written a blog post that outlines this 2 approach using an example. Here's the essence 1 of it:

describe('service interface', function(){
    function createInstance(){
        return /* code to create a new service or pass in an existing reference */
    }

    executeSharedTests(createInstance);
});

function executeSharedTests(createInstanceFn){
    describe('when adding a new menu entry', function(){
        var subjectUnderTest;

        beforeEach(function(){
            //create an instance by invoking the constructor function
            subjectUnderTest = createInstanceFn();
        });

        it('should allow to add new menu entries', function(){
            /* assertion code here, verifying subjectUnderTest works properly */
        });
    });
}
Score: 2

There's a nice article on thoughbot's website: https://robots.thoughtbot.com/jasmine-and-shared-examples

Here's 2 a brief sample:

appNamespace.jasmine.sharedExamples = {
  "rectangle": function() {
    it("has four sides", function() {
      expect(this.subject.sides).toEqual(4);
    });
  },
 };

And with some underscore 1 functions to define itShouldBehaveLike

window.itShouldBehaveLike = function() {
  var exampleName      = _.first(arguments),
      exampleArguments = _.select(_.rest(arguments), function(arg) { return !_.isFunction(arg); }),
      innerBlock       = _.detect(arguments, function(arg) { return _.isFunction(arg); }),
      exampleGroup     = appNamespace.jasmine.sharedExamples[exampleName];

  if(exampleGroup) {
    return describe(exampleName, function() {
      exampleGroup.apply(this, exampleArguments);
      if(innerBlock) { innerBlock(); }
    });
  } else {
    return it("cannot find shared behavior: '" + exampleName + "'", function() {
      expect(false).toEqual(true);
    });
  }
};
Score: 2

Let me summarize it with working example

  describe('test', function () {

    beforeEach(function () {
      this.shared = 1;
    });

    it('should test shared', function () {
      expect(this.shared).toBe(1);
    });

    testShared();
  });

  function testShared() {
    it('should test in function', function() {
      expect(this.shared).toBe(1);
  });

  }

The 11 crucial parts here are this keyword to pass 10 context and because of this we have to use 9 "normal" functions (another crucial part).

For production code 8 I would probably use normal function only 7 in beforeEach to pass/extract context but keep to 6 use arrow-function in specs for brevity.

Passing 5 context as parameter wouldn't work because 4 normally we define context in beforeEach block wich 3 invoked after.

Having describe section seems not 2 important, but still welcome for better 1 structure

Score: 1

This is similar to starmer's answer, but 6 after working through it I found some differences 5 to point out. The downside is that if the 4 spec fails you just see 'should adhere to 3 common saving specifications' in the Jasmine 2 report. The stack trace is the only way 1 to find where it failed.

// common specs to execute
self.executeCommonSpecifications = function (vm) {
  // I found having the describe( wrapper here doesn't work
  self.shouldCallTheDisplayModelsSaveMethod(vm);
}
self.shouldCallTheDisplaysSaveMethod = function (vm) {
  expect(vm.save.calls.count()).toBe(1);
};

// spec add an it so that the beforeEach is called before calling this
beforeEach(function(){
  // this gets called if wrapped in the it
  vm.saveChanges();
}
it('should adhere to common saving specifications', function () {
  executeSavingDisplaysCommonSpecifications(vm);
});
Score: 1

This is the approach I have taken, inspired 11 by this article:

https://gist.github.com/traviskaufman/11131303

which is based on Jasmine 10 own documentation:

http://jasmine.github.io/2.0/introduction.html#section-The_%3Ccode%3Ethis%3C/code%3E_keyword

By setting shared dependencies 9 as properties of beforeEach function prototype, you 8 can extend beforeEach to make this dependencies available 7 via this.

Example:

describe('A suite', function() {
    // Shared setup for nested suites
    beforeEach(function() {
        // For the sake of simplicity this is just a string
        // but it could be anything
        this.sharedDependency = 'Some dependency';
    });

    describe('A nested suite', function() {
        var dependency;

        beforeEach(function() {
            // This works!
            dependency = this.sharedDependency;                
        });

        it('Dependency should be defined', function() {
            expect(dependency).toBeDefined();
        });
    });

    describe('Check if string split method works', function() {
        var splitToArray;

        beforeEach(function() {
            splitToArray = this.sharedDependency.split();                
        });

        it('Some other test', function() { ... });
    });
});

I know my example is kind of 6 useless but it should serve its purpose 5 as code example.

Of course this is just one 4 of the many things you could do to achieve 3 what you say, I'm sure that more complex 2 design patterns may be applied on top or 1 aside to this one.

Hope it helps!

Score: 1

Here is a simpler solution. Declare a variable 4 function and use it, without using the this 3 keyword or context:

describe("Test Suit", function ()
{
   var TestCommonFunction = function(inputObjects)
   {
     //common code here or return objects and functions here etc
   };

   it("Should do x and y", function()
   {
       //Prepare someInputObjects
       TestCommonFunction(someInputObjects);
       //do the rest of the test or evaluation
   });
});

You can also return an 2 object with more functions and call the 1 returned functions thereafter.

Score: 0

I wasn't hapy about having to instantiate 9 a whole new Component for my Angular tests 8 that were sharing logic on different function 7 calls (e.g. a private function called from 6 two separate public functions on the component). The 5 TestBed intantiates the component and doing 4 so by hand just to run shared tests seemed 3 dirty to me. I fixed my issue by avoiding 2 the component reference altogether and calling 1 functions by their name in the shared testsuite:

describe('firstFunction', () => {
  itShouldExecuteSharedFunction('firstFunction');
});

describe('secondFunction', () => {
  itShouldExecuteSharedFunction('secondFunction');
});

function itShouldExecuteSharedFunction(fnName: string) {
 describe('sharedFunction', () => {

   beforeEach(() => {...});

   afterEach(() => {...});

   it('should do something', () => {
     component[fnName]();        
     expect(...);
   });
 });
}

More Related questions