Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jasmine parameterized unit test

Okay as a C# NUnit guy this might be odd.

But does jasmine allow parameterized unit test?

I am not sure if it goes against the "declare" and "it" to make things readable to non programmers.

I have seen some third party plug ins but they are kind of old, not sure if it has been added to jasmine. If I am ment to use a plug in

Just to help anyone who finds this in the future, I have been told on jasmine forum There is no first class support for parameterized tests within Jasmine itself.

like image 474
Ashley Kilgour Avatar asked Jul 29 '16 13:07

Ashley Kilgour


People also ask

Is Jasmine unit test?

Jasmine is a very popular JavaScript behavior-driven development (In BDD, you write tests before writing actual code) framework for unit testing JavaScript applications. It provides utilities that can be used to run automated tests for both synchronous and asynchronous code.

Does jest support parameterized tests?

Jest has a built-in support for tests parameterized with data table that can be provided either by an array of arrays or as tagged template literal.


2 Answers

Based on piotrek's answer and the article Parameterized testing in Javascript, you could also use the following approach which uses ES6 syntax:

[   ['abc', 3],   ['ab', 2],   ['', 0], ].forEach(([string, expectedLength]) => {   it(`should return length ${expectedLength} for string "${string}"`, () => {     expect(string.length).toBe(expectedLength);   }); }); 

I have tested it with the Jest test framework, but it should work with Jasmine as well.

like image 62
Marco Eckstein Avatar answered Sep 20 '22 17:09

Marco Eckstein


Better solution (especially if you use TypeScript)

Another solution is to use Array of Objects instead of Array of Arrays. It fits better if you use some typing system like TypeScript.


Type issue

Imagine you have the following parametrised test:

it('action(value) should reset the forms pool only if value is true', () => {   [     [true, 1],     [false, 0],   ].forEach(([value, calledTimes]) => {     spyResetFormsPool.calls.reset();      component.action(value); // type error #1      expect(spyResetFormsPool).toHaveBeenCalledTimes(calledTimes); // type error #2   }); });      

with TypeScript, it will fail to compile giving two errors:

error #1:

error TS2345: Argument of type 'number | boolean' is not assignable to parameter of type 'boolean'.

error #2:

error TS2345: Argument of type 'number | boolean' is not assignable to parameter of type 'number'. Type 'true' is not assignable to type 'number'.

That is because TypeScript sees an array of 'number | boolean'.

We could quickly solve this warning by using some explicit cast:

it('action(value) should reset the forms pool only if value is true', () => {   [     [true, 1],     [false, 0],   ].forEach(([value, calledTimes]) => {     spyResetFormsPool.calls.reset();      component.action(value as boolean); // necessary cast      expect(spyResetFormsPool).toHaveBeenCalledTimes(calledTimes as number);  // necessary cast   }); }); 

however this solution is not very nice.


Solution

A better way is to use Array of Objects, so the types are correctly handled by default and there is no need of explicit casting:

it('action(value) should reset the forms pool only if value is true', () => {   [     { value: true, calledTimes: 1 },     { value: false, calledTimes: 0 },   ].forEach(({ value, calledTimes }) => {     spyResetFormsPool.calls.reset();      component.action(value);      expect(spyResetFormsPool).toHaveBeenCalledTimes(calledTimes);   }); }); 

Do you want to use for instead of forEach (I personally find it more readable)? That's also possible:

it('action(value) should reset the forms pool only if value is true', () => {   for (const {value, calledTimes} of [     {value: true, calledTimes: 1},     {value: false, calledTimes: 0},   ]) {     spyResetFormsPool.calls.reset();      component.action(value);      expect(spyResetFormsPool).toHaveBeenCalledTimes(calledTimes);   } }); 

Alternatively, you can also move the it inside the loop. When I do this, I usually add a testId to each object so I can keep track of which tests are failing:

for (const {value, calledTimes} of [   { testId: 1, value: true,  calledTimes: 1 },   { testId: 2, value: false, calledTimes: 0 }, ]) {   it(`action(value) should reset the forms pool only if value is true [${testId}]`, () => {     spyResetFormsPool.calls.reset();      component.action(value);      expect(spyResetFormsPool).toHaveBeenCalledTimes(calledTimes);   }); } 
like image 42
Francesco Borzi Avatar answered Sep 21 '22 17:09

Francesco Borzi