I want to test a Typescript function with lots of varied inputs to ensure good coverage. How do I go about doing this 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 it to every time.
Copy and Paste
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 does greatly reduce 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 this 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 report coverage, as well as avoiding 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 help 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.each function which looks promising. Anyone had success with this?
Stephen Cooper - Senior Developer at AG Grid
Follow me on Twitter @ScooperDev or Tweet me about this post.