jasmine parameterized unit test
Solution 1
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.
Solution 2
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);
});
}
Solution 3
You can use the following convention to increase readability:
const testCases = [
{actualValue: true, expectedValue: true},
{actualValue: false, expectedValue: false}
]
testCases.forEach(({actualValue, expectedValue}) => {
it(`should be the same given: ${actualValue} and expected :${expectedValue} values`, () => {
expect(actualValue).toBe(expectedValue)
})
})
You'll see the following test cases to run:
Test Results
+ should be the same given: true and expected: true values
+ should be the same given: false and expected: false values
Solution 4
i haven't worked with jasmine since a long time but it was pretty easy to add parameterized tests:
['abc', 3,
'ab', 4,
'', 0].
it('should contain string length', function(string, expected){
expect(string.length).toBe(expected);
});
with just a few lines of infrastructure code:
Array.prototype.it = function(description, testCaseFunction) {
_(this)
.chunk(testCaseFunction.length)
.each(function(innerArray){
it(description + ' ' + JSON.stringify(innerArray), function(){
testCaseFunction.apply(this, innerArray);
});
})
.value();
};
depending on your desired syntax and willingness to change default js objects, you have plenty of options: http://blog.piotrturski.net/2015/04/jasmine-parameterized-tests.html
Ashley Kilgour
C# coder, believes in TDD, and having unit tests to make sure I haven't broke anything.
Updated on June 26, 2022Comments
-
Ashley Kilgour about 2 years
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.
-
Astridax almost 5 yearsIt ought to be added that if you have a beforeEach block and possibly an afterEach block this entirely breaks. If you try and set up a test this way with a beforeEach block Jest throws a 'TypeError: Cannot read property '[object Array]' of undefined' exception. However all is not lost, it seems to work if you assign the test cases to a variable and iterate over that, as per Dymtro's answer stackoverflow.com/a/55909737/1392407