Introducing test cases​ to Jasmine

If I want to test a TypeScript function with lots of varied inputs to ensure good coverage, how do I go about it without copy and pasting a lot of test scaffolding code?

For this post, we want to test a toCamelCase formatter with lots of different cases to make sure it does what we expect every time.

Copy and Paste

The initial approach is to copy and paste the test, changing the input and expected result for each case. While this gives us good coverage, there is a lot of repeated code to maintain in the future. This is also asking for a copy-paste error!

describe('Test toCamelCase formatter: ', () => {

  beforeEach(() => {
    // Test setup code
  });

  it('No Change', () => {
    expect(toCamelCase('test')).toBe('test');
  });

  it('Single Word Case', () => {
    expect(toCamelCase('TeST')).toBe('test');
  });

  it('Two Words', () => {
    expect(toCamelCase('test Me')).toBe('testMe');
  });

  it('Two Words Case', () => {
    expect(toCamelCase('TEST ME')).toBe('testMe');
  });
});

All in a single test

To reduce the amount of copied test code, we could put all the inputs and expectations in a single test. This greatly reduces the amount of test code, but we lose visibility on each test case and some cases may not run if a previous check fails.

This also breaks tests that require setup via the beforeEach() method as it is not run between each test.

describe('Test toCamelCase formatter: ', () => {

  beforeEach(() => {
    // POTENTIAL BUG: Test setup code not run between each expectation!
  });

  it('All Tests', () => {
    expect(toCamelCase('test')).toBe('test');
    expect(toCamelCase('TeST')).toBe('test');
    expect(toCamelCase('test Me')).toBe('testMe');
    expect(toCamelCase('TEST ME')).toBe('testMe');
  });

});

ForEach your it() calls

What we really want is to define a list of test cases and run each through the same test function. This means every test is run independently and we have no copy and pasting of test code!

To achieve this, we first create an array of test cases using the TestCase interface.

interface TestCase { name: string, value: string, expected: string };

const testCases : TestCase[] = [
  {name: 'No Change', value: 'test', expected: 'test'},
  {name: 'Single Word Case', value: 'TesT', expected: 'test'},
  {name: 'Two Words', value: 'test ME', expected: 'testMe'},
  {name: 'Two Words Case', value: 'TEST ME', expected: 'testMe'},
];

Then, we define our test it() function within a foreach loop of the testCases array.


describe('Test toCamelCase formatter: ', () => {

  beforeEach(() => {
    // Test setup code
  });

  testCases.forEach(tc =>
    {
        it(tc.name, () => {
            expect(toCamelCase(tc.value)).toBe(tc.expected);
        });
    });
    
});

Now, every test case is run individually, giving full test coverage, and avoids the beforeEach bug above. Happy days!

I got this idea from a tweet by WesGrimes so thanks to him for sharing it! In writing this post, I came across a Jasmine plugin called Jasmine Data Driven Tests by Greg Burghardt which looks to make these data-driven​ tests even shorter to write.

Plugin or no plugin, this little trick has helped me delete a lot of duplicated test code and encouraged me to improve test coverage by simplifying the creation of new test cases.

I would be interested to know if other testing libraries handle this natively. I notice that Jest has an describe.eachhttps://jestjs.io/docs/api function, which looks promising.

Free Resources

Attributions:
  1. undefined by undefined