The long way through Software Craftsmanship

Comparing options for parametrized testing in javascript

Feb 26, 2017 - 3 minute read - Comments - javascripttestparametrized-testingjavacsharpnunitjunitcomparisonetudestudy

We explore how to use parametrized tests (example in java, example in c#)

The tests are written using mocha syntax: describe, it. Using chai for expectations: assert.

Note: the example used below (adding to an array) is simple enough to be understood by everybody, without having to explain the domain. The real tests can have a more complicated environment or domain. This is just an example.

Using a custom syntax

This is just an example of some possible syntax, not using any (currently) existing framework.

describe('adding to a list', ()=>{
  newTestCase({description: 'to an empty list',
               input: [],
               parameter: 2,
               expected: [2]})
  newTestCase({description: 'to a non-empty list',
               input: [1],
               parameter: 2,
               expected: [1, 2]})

  it(`${testCase.description}`, () => {
    testCase.input.push(testCase.parameter)
    expect(testCase.input).to.deep.equal(testCase.expected)
  })
})
Pros Cons
flexible
verbose
too close to the the real test syntax?
how to explain that the it will execute all test cases?
magic variable testCase: linting, editor complaining about it

Using an in-place executor

describe('adding to a list', ()=>{
  const runs = [{description: 'to an empty list',
               input: [],
               parameter: 2,
               expected: [2]},
               {description: 'to a non-empty list',
               input: [1],
               parameter: 2,
               expected: [1, 2]}]

  runs.forEach(testCase => {
    it(`${testCase.description}`, () => {
      testCase.input.push(testCase.parameter)
      expect(testCase.input).to.deep.equal(testCase.expected)
    })
  }
})
Pros Cons
flexible
flexible to use any testCase variable name (e.g., testCase, tC, run)
verbose
using the real test syntax
duplicate the test runner in every case
test-related features mixed with business/domain tests

Using it, DAMP

describe('adding to a list', ()=>{
  it('to an empty list', () => {
    const input= [];
    const parameter= 2;
    const expected= [2];
    input.push(parameter);
    expect(input).to.deep.equal(expected);
  });

  it('to a non-empty list', () => {
    const input= [1];
    const parameter= 2;
    const expected= [1, 2];
    input.push(parameter);
    expect(input).to.deep.equal(expected);
  });
});
Pros Cons
one test per case
the test is self-contained (DAMP)

Using it, DRY

describe('adding to a list', ()=>{
  it('to an empty list', () => {
    const input= [];
    const parameter= 2;
    const expected= [2];
    // use explaining variables
    pushingToMatches(parameter, input, expected);
  });

  it('to a non-empty list', () => {
    // not using explaining variables
    pushingToMatches(2, [1], [1, 2]);
  });

  function pushingToMatches(parameter, input, expected) {
    input.push(parameter);
    expect(input).to.deep.equal(expected);
  }
});
Pros Cons
one test per case
no repeated code
the testing method pushingToMatches requires access to all test inputs and outputs
the testing method pushingToMatches breaks the SRP: act and assert

Using it, DRY+Fluent

describe('adding to a list', ()=>{
  it('to an empty list', () => {
    const input= [];
    const parameter= 2;
    const expected= [2];
    // use explaining variables
    pushingTo(parameter, input).matches(expected);
  });

  it('to a non-empty list', () => {
    // not using explaining variables
    pushingTo(2, [1]).matches([1, 2]);
  });

  function pushingTo(parameter, input) {
    input.push(parameter);
    return {matches: expected => expect(input).to.deep.equal(expected)};
  }
});
Pros Cons
one test per case
no repeated code
readable
verbose, one test per case, when this could be expressed in some other way
the testing method pushingTo creates space for the difference in act and assert

Sources

(This post is a modified version of the session available here)

Disclaimer about AI/GenAI

As of 2026-05-06, the text in these articles and blog entries has been written without AI/GenAI, except I sometimes use a spellchecker to fix errors. Think Word's spellchecker, not ChatGPT.

Notes, as of today (2026-05-06):

  • No code snippet has been automatically generated, nor vibe-coded, nor generated and reviewed.
  • I don’t have any article with AI contribution.

For future entries:

  • I may have used GenAI for the code in the repo. The code I exemplify/copy in the article will always be reviewed and tested, not vibe-coded. I will specify it in each snippet or at the top/bottom of the article.
  • I normally don't use it for the text contents, although if I have used it for the article text, it would be indicated as such.

Any entry before 2026-05-06 does not contain any AI/GenAI.

For more information, read the AI/GenAI Policy

Self-Study in February 2017 Self-Study in March 2017