What's a good way to reuse test code using Jasmine?

前端 未结 8 599
無奈伤痛
無奈伤痛 2020-12-05 07:01

I\'m using the Jasmine BDD Javascript library and really enjoying it. I have test code that I\'d like to reuse (for example, testing multiple implementations of a base clas

相关标签:
8条回答
  • 2020-12-05 07:41

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

    Here's a brief sample:

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

    And with some underscore 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);
        });
      }
    };
    
    0 讨论(0)
  • 2020-12-05 07:45

    This is similar to starmer's answer, but after working through it I found some differences to point out. The downside is that if the spec fails you just see 'should adhere to common saving specifications' in the Jasmine report. The stack trace is the only way 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);
    });
    
    0 讨论(0)
  • 2020-12-05 07:45

    It was pointed out to me to wrap a describe call in a function that passes it a parameter.

    0 讨论(0)
  • 2020-12-05 07:46

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

    Instead what you have to do (as mentioned by @moefinley) is to pass in a reference to a constructor function instead. I've written a blog post that outlines this approach using an example. Here's the essence 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 */
            });
        });
    }
    
    0 讨论(0)
  • 2020-12-05 07:49

    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 crucial parts here are this keyword to pass context and because of this we have to use "normal" functions (another crucial part).

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

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

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

    0 讨论(0)
  • 2020-12-05 07:51

    Here is a simpler solution. Declare a variable function and use it, without using the this 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 object with more functions and call the returned functions thereafter.

    0 讨论(0)
提交回复
热议问题