简体   繁体   中英

Unit-testing multiple use cases with Karma & Mocha.js

I'm fairly new to testing, so this question is about best-practices and how this test ought to be written. I'm using Karma with Mocha and Chai to test an Angular.js app.

I'm currently testing a function that counts the number of ways to arrange a combination of letters in a specific order. It follows a pattern to pull letters from either the set of consonants or the set of vowels.

These are my current tests:

describe("should count the number of combinations correctly", function() {
  describe("with 2-item arrays", function() {
    beforeEach(function(){
      scope.vowels = ['a', 'e'];
      scope.consonants = ['b', 'c'];
    })

    it("with 2 letters in pattern", function() {
      scope.pattern = 'CV';
      expect(scope.combinationCounter()).to.equal(4);
    });

    it("with 3 letters in pattern", function() {
      scope.pattern = 'CVC';
      expect(scope.combinationCounter()).to.equal(8);
    });

    it("with 4 letters in pattern", function() {
      scope.pattern = 'CVCV';
      expect(scope.combinationCounter()).to.equal(16);
    });
  });
  describe("with 3-item arrays", function() {
    beforeEach(function(){
      scope.vowels = ['a', 'e', 'i'];
      scope.consonants = ['b', 'c', 'd'];
    })

    it("with 2 letters in pattern", function() {
      scope.pattern = 'CV';
      expect(scope.combinationCounter()).to.equal(9);
    });

    it("with 3 letters in pattern", function() {
      scope.pattern = 'CVC';
      expect(scope.combinationCounter()).to.equal(27);
    });

    it("with 4 letters in pattern", function() {
      scope.pattern = 'CVCV';
      expect(scope.combinationCounter()).to.equal(81);
    });        
  });
});

These tests work, and they give me useful error messages, but I can't help feeling like I'm doing some unnecessary repetition, since I'm essentially performing the same tests with different values.

Is there a way to write these tests that maintains the error message structure, but doesn't require me to write out

it("with x letters in pattern", function() {
  scope.pattern = 'whatever';
  expect(scope.combinationCounter()).to.equal(y);
});  

every single time?

I'd like to test a larger numbers of cases without a huge test file, but keep the error messages readable.

Edit: question answered by @Stas

@Stas gave me the right answer, which is to use a loop with an object to hold the different test cases. However, since his answer was written with lodash, which I was not using, I've included my final loop code below for reference.

The scenarios object I am looping over here is identical to the one in @Stas' example.

for (var x in scenarios) {
  var vowels     = scenarios[x].arrays.vowels;
  var consonants = scenarios[x].arrays.consonants;
  var v = vowels;
  var c = consonants;
  describeStuff();
}

function describeStuff(){ 
  describe("with " + x, function(){
    setLetters(v,c);
  });
}

function setLetters(vowels, consonants){
  describe("("+vowels + ' & ' + consonants + "),", function(){
    beforeEach(function(){
      scope.vowels = vowels;
      scope.consonants = consonants;
    });
    innerLoop();
  });
}

function innerLoop(){
  for (var y in scenarios[x].combinations) {
    var combinations = scenarios[x].combinations;
    var pat = scenarios[x].combinations[y].pattern;
    var res = scenarios[x].combinations[y].result;
    setResults(pat, res);
  }
}

function setResults(p, r){
  var pattern = p;
  var result = r;
  it("with " + p.length + " letters in pattern (" + p + ")", function(){
    scope.pattern = pattern;
    expect(scope.combinationCounter()).to.equal(result);
  });
}

I had to write the loop using a chain of four functions that call each other, because using Mocha's callback syntax inside the for in loop causes only the final test cases to be saved to the function variables. Defining the functions outside the loop and then calling them inside resolves this issue.

You can create scenario objects and wrap the execution in for each loop (I'm using lodash in this example):

describe("should count the number of combinations correctly", function () {

    var scenarios = {
        "2-item arrays": {
            arrays: {
                vowels: ['a', 'e'],
                consonants: ['b', 'c']
            },
            combinations: [
                {pattern: "CV", result: 4},
                {pattern: "CVC", result: 8},
                {pattern: "CVCV", result: 16}
            ]
        },
        "3-item arrays": {
            arrays: {
                vowels: ['a', 'e', 'i'],
                consonants: ['b', 'c', 'd']
            },
            combinations: [
                {pattern: "CV", result: 9},
                {pattern: "CVC", result: 27},
                {pattern: "CVCV", result: 81}
            ]
        }
    };

    _.forEach(scenarios, function (scenario, key) {
        describe("with " + scenario.key, function () {
            beforeEach(function () {
                scope.vowels = scenario.arrays.vowels;
                scope.consonants = scenario.arrays.consonants;
            });

            _.forEach(scenario.combinations, function(combination) {
                it("with " + combination.pattern.length + " letters in pattern", function() {
                    scope.pattern = combination.pattern;
                    expect(scope.combinationCounter()).to.equal(combination.result);
                });
            })
        });
    });
});

This way you can add more scenarios without duplicating describe() / beforeEach() / it() for each combination and it will produce the same messages.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM