jasmine parameterized unit test - unit-testing

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.

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.

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);
});
}

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

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

So I started combining:
YAML using js-yaml
jasminejs
Typescript
to create what I believe are readable parameterized tests like this:
import YamlTableReader, {fixtureData, TestData} from "./YamlTableReader";
describe("TestSuite", () => {
describe("Real TestCase with Data Fixture", () => {
// now using tagged template-string to auto convert into YamlTableReader.
var testdata = fixtureData `
| ID | Value1 | Value2 | Squared |
| 0 |1 | 1 | 1 |
| 1 |2 | 2 | 4 |
| 2 |3 | 3 | 91 |
`;
// This actually creates a test for each row of the table above
testdata.describeEach("Square Test","[ID={ID}]:{Value1} x {Value2} should be equal to {Squared}",
(row: {Value1: number, Value2: number, Squared: number}) => {
expect((row.Value1 * row.Value2)).toBe(row.Squared)
}
);
});
Running this will give the following results:
Failures:
1) TestSuite 2 Real TestCase with Data Fixture Square Test : [ID=2]:3 x 3 should be equal to 91
Message:
Expected 9 to be 91.
Sources: https://github.com/deicongmbh/jasmine-param-tests

Related

Writing tests for RxJS that uses retryWhen operator (understanding difference from retry operator)

I'm trying to write tests for the following function that uses retryWhen operator:
// some API I'm using and mocking out in test
import { geoApi } from "api/observable";
export default function retryEpic(actions$) {
return actions$.pipe(
filter(action => action === 'A'),
switchMap(action => {
return of(action).pipe(
mergeMap(() => geoApi.ipLocation$()),
map(data => ({ data })),
retryWhen(errors => {
return errors.pipe(take(2));
}),
);
}),
);
}
The code is supposed to perform a request to some remote API geoApi.ipLocation$(). If it gets an error, it retries 2 times before giving up.
I have written the following test code that uses Jest and RxJS TestScheduler:
function basicTestScheduler() {
return new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
}
const mockApi = jest.fn();
jest.mock('api/observable', () => {
return {
geoApi: {
ipLocation$: (...args) => mockApi(...args),
},
};
});
describe('retryEpic()', () => {
it('retries fetching 2 times before succeeding', () => {
basicTestScheduler().run(({ hot, cold, expectObservable, expectSubscriptions }) => {
const actions$ = hot('-A');
// The first two requests fail, third one succeeds
const stream1 = cold('-#', {}, new Error('Network fail'));
const stream2 = cold('-#', {}, new Error('Network fail'));
const stream3 = cold('-r', { r: 123 });
mockApi.mockImplementationOnce(() => stream1);
mockApi.mockImplementationOnce(() => stream2);
mockApi.mockImplementationOnce(() => stream3);
expectObservable(retryEpic(actions$)).toBe('----S', {
S: { data: 123 },
});
expectSubscriptions(stream1.subscriptions).toBe('-^!');
expectSubscriptions(stream2.subscriptions).toBe('--^!');
expectSubscriptions(stream3.subscriptions).toBe('---^');
});
});
});
This test fails.
However, when I replace retryWhen(...) with simply retry(2), then the test succeeds.
Looks like I don't quite understand how to implement retry with retryWhen. I suspect this take(2) is closing the stream and kind of preventing everything from continuing. But I don't quite understand it.
I actually want to write some additional logic inside retryWhen(), but first I need to understand how to properly implement retry() with retryWhen(). Or perhaps that's actually not possible?
Additional resources
My implementation of retryWhen + take was based on this SO answer:
How to create an RXjs RetryWhen with delay and limit on tries
Official docs:
retryWhen
You can use retryWhen for those two purposes, one to have your logic in it and the second is the retry numbers you'd like to give it (no need to use retry operator):
// some API I'm using and mocking out in test
import { geoApi } from "api/observable";
export default function retryEpic(actions$) {
return actions$.pipe(
filter(action => action === 'A'),
switchMap(action => {
return of(action).pipe(
mergeMap(() => geoApi.ipLocation$()),
map(data => ({ data })),
retryWhen(errors =>
errors.pipe(
mergeMap((error, i) => {
if (i === 2) {
throw Error();
}
// return your condition code
})
)
)
)
}),
);
}
Here is a simple DEMO of that.
As for understanding this logic:
retryWhen and retry operators, according to the Official docs you've referenced:
resubscribing to the source Observable (if no error or complete executes)
This is why you can't pipe retry and retryWhen together. You can say that these operators are a chain breakers...

Vue only export "methods" to Jest when I ran unit test, how to export "data" and others?

I have a very small Vue project, looks like this:
the to be test file: src/views/Sum.vue
<template>
<div>
Sum of ({{a}},{{b}}) is: {{sum()}}
</div>
</template>
<script>
export default{
data: function(){
return {
a: 1,
b: 2
}
},
methods: {
sum: function(){
console.info("-- in Sum.vue, this: ", this)
return this.a + this.b
}
}
}
</script>
and the jest test file looks like:
import { shallowMount } from "#vue/test-utils"
import Sum from '#/views/Sum.vue'
describe('Sum.vue', () => {
it('should run sum', () => {
console.info("-- in sum.spec.js, Sum is: " )
console.info(Sum)
expect(Sum.methods.sum()).toBe(3)
})
})
when I ran the test by $ npm run test:unit, I got errors:
-- in sum.spec.js, Sum is:
{ data: [Function: data],
methods: { sum: [Function: sum] },
render: [Function: render],
staticRenderFns: [] }
-- in Sum.vue, this: { sum: [Function: sum] }
● Sum.vue › should run sum
expect(received).toBe(expected) // Object.is equality
Expected: 3
Received: NaN
6 | console.info("-- in sum.spec.js, Sum is: " )
7 | console.info(Sum)
> 8 | expect(Sum.methods.sum()).toBe(3)
| ^
9 | })
10 | })
11 |
at Object.it (tests/unit/say_one.spec.js:8:31)
It looks like the this acts different in this two context:
in the spec ( this = Sum.methods)
in the implementation code ( this = [Sum.data, Sum.methods, Sum.render])
so my question is:
How to make unit tests to the methods that referenced the data variables? (just like the above code )
thanks so much!
OK, I got it.
thanks to #Alexander Staroselsky, I should use wrapper.vm instead of `Sum' in my code.
the correct unit test file should be:
import { shallowMount } from "#vue/test-utils"
import Sum from '#/views/Sum.vue'
describe('Sum.vue', () => {
it('should run sum', () => {
// expect(Sum.methods.sum()).toBe(3) <-- here I used Sum.methods.sum()
// below is correct.
const wrapper = shallowMount(Sum)
expect(wrapper.vm.sum()).toBe(3)
})
})
the wrapper.vm is interesting object, you can visit the variable and methods directly, like :
wrapper.vm.a # => vue.data.a
wrapper.vm.b # => vue.data.b
wrapper.vm.sum # => vue.methods.sum
so the code shallowMount() is so important whatever you want to test the HTML output or not, you should write this line of code.

Marble testing a subject's behavior over time with jasmine-marbles

So I'm trying to test a Subject's behavior and it's not working, and it seems like there some things I'm not understanding correctly. Consider the following test:
it('marble testing subject test', () => {
const obs$: Subject<number> = new Subject<number>();
obs$.next(42);
obs$.next(24);
expect(obs$.asObservable()).toBeObservable(hot('xy', { x: 42, y: 24 }));
});
This fails with this error message:
Expected $.length = 0 to equal 2.
Expected $[0] = undefined to equal Object({ frame: 0, notification: Notification({ kind: 'N', value: 42, error: undefined, hasValue: true }) }).
Expected $[1] = undefined to equal Object({ frame: 10, notification: Notification({ kind: 'N', value: 24, error: undefined, hasValue: true }) }).
I think I sort of understand why: the Subject (per documentation) only emits values after the start of the subscription. The toBeObservable() function (I'm assuming) subscribes to the Subject and so my 2 next calls happened before this, so they won't emit.
So my question is, how would I test something like this? i.e. testing a series of emissions from a subject over time? Is this possible to do with marble testing? I can sort of get it to work by changing it out for a ReplaySubject but if I do, the marble diagram has to be (xy) instead of xy.
Thanks.
I have this working in the context of an Angular application
My service, the key part being that I define my things as a getter, giving me a chance to actually spy on asObservable if I just defined things$ as a property then the spy doesn't work:
#Injectable({
providedIn: 'root'
})
export class ApiService {
private things = new BehaviorSubject<Array<string>>([]);
public get things$(): Observable<boolean> {
return this.things.asObservable().pipe(map((things) => things.length > 0))
}
}
And then in my test, I guess the key part is that I am spying on the asObservable method on the things BehaviourSubject. Like this:
describe('#things$', () => {
it('should ', () => {
const things. = 'a-b-a';
const expected = 't-f-t';
// spy on
spyOn(service['things'], 'asObservable').and.returnValue(hot(things, {
a: ['a', 'b'],
b: []
}));
expect(service.things$).toBeObservable(cold(expected, {
t: true,
f: false
}));
});
});

How to test timeout() in a rxjs pipe with jasmine-marbles

I have written a pipe that filters an input observable. In the pipe I specify a timeout with the timeout() operator to abort waiting if the expected value is not emitted by the source in time.
I want to test the timeout case with jasmine-marbles, but I can't get it to work.
I believe that expect(source).toBeObservable() evaluates before the source emits.
see Stackblitz
The pipe to be tested:
source = cold('a', { a: { id: 'a' } }).pipe(
timeout(500),
filter((a) => false),
catchError((err) => {
return of({ timeout: true })
}),
take(1)
);
Testing with toPromise() works as expected:
expect(await source.toPromise()).toEqual({ timeout: true });
Testing with jasmine-marbles
const expected = cold('500ms (a|)', { a: { timeout: true } });
expect(source).toBeObservable(expected);
fails with the error
Expected $.length = 0 to equal 2.
Expected $[0] = undefined to equal Object({ frame: 500, notification: Notification({ kind: 'N', value: Object({ timeout: true }), error: undefined, hasValue: true }) }).
Expected $[1] = undefined to equal Object({ frame: 500, notification: Notification({ kind: 'C', value: undefined, error: undefined, hasValue: false }) }).
Support for time progression was recently added (see jasmine-marbles PR #38) to jasmine-marbles 0.5.0. Additional test specs were added to the package that demonstrate one of a couple of possible ways to accomplish what you want. Here are some options I was able to throw together using your Stackblitz sample.
Option 1
When you initialize the source observable outside the test method (e.g. in beforeEach), you must explicitly initialize and pass the test scheduler to timeout to get expect().toBeObservable() working. However, take note that this change will break the "should work with toPromise" test. (I don't know why, but toPromise() doesn't appear to work with this approach.)
describe('Marble testing with timeout', () => {
let source;
beforeEach(() => {
// You must explicitly init the test scheduler in `beforeEach`.
initTestScheduler()
source = cold('a', { a: { id: 'a' } }).pipe(
// You must explicitly pass the test scheduler.
timeout(500, getTestScheduler()),
filter((a) => false),
catchError(err => {
return of({ timeout: true })
}),
take(1)
);
});
it('should work with toBeObservable', () => {
const expected = cold('500ms (a|)', { a: { timeout: true } });
expect(source).toBeObservable(expected);
});
});
Option 2
You can refactor things slightly and initialize the source observable inside the test method (not in beforeEach). You don't need to explicitly initializes the test scheduler (jasmine-marbles will do it for you before the test method runs), but you still have to pass it to timeout. Note how the createSource function can be used with the test scheduler or the default scheduler (if the scheduler argument is left undefined). This options works with both the "should work with toPromise" test and the "should work with toBeObservable" test.
describe('Marble testing with timeout', () => {
const createSource = (scheduler = undefined) => {
return cold('a', { a: { id: 'a' } }).pipe(
// You must explicitly pass the test scheduler (or undefined to use the default scheduler).
timeout(500, scheduler),
filter((a) => false),
catchError(err => {
return of({ timeout: true })
}),
take(1)
);
};
it('should work with toPromise', async () => {
const source = createSource();
expect(await source.toPromise()).toEqual({ timeout: true });
});
it('should work with toBeObservable', () => {
const source = createSource(getTestScheduler());
const expected = cold('500ms (a|)', { a: { timeout: true } });
expect(source).toBeObservable(expected);
});
});
Option 3
Finally, you can skip passing the test scheduler to timeout if you explicitly use the test scheduler's run method, but you must use expectObservable (as opposed to expect().toBeObservable(). It works just fine, but Jasmine will report the warning "SPEC HAS NO EXPECTATIONS".
describe('Marble testing with timeout', () => {
let source;
beforeEach(() => {
source = cold('a', { a: { id: 'a' } }).pipe(
timeout(500),
filter((a) => false),
catchError(err => {
return of({ timeout: true })
}),
take(1)
);
});
it('should work with scheduler and expectObservable', () => {
const scheduler = getTestScheduler();
scheduler.run(({ expectObservable }) => {
expectObservable(source).toBe('500ms (0|)', [{ timeout: true }]);
});
});
});

parametrized tests with Mocha

How can I create parametrized tests with Mocha?
Sample use case: I have 10 classes, that are 10 different implementations of the same interface. I want to run the exact same suit of tests for each class. I can create a function, that takes the class as a parameter and runs all tests with that class, but then I will have all tests in a single function - I won't be able to separate them nicely to different "describe" clauses...
Is there a natural way to do this in Mocha?
You do not need async package. You can use forEach loop directly:
[1,2,3].forEach(function (itemNumber) {
describe("Test # " + itemNumber, function () {
it("should be a number", function (done) {
expect(itemNumber).to.be.a('number')
expect(itemNumber).to.be(itemNumber)
});
});
});
I know this was posted a while ago but there is now a node module that makes this really easy!! mocha param
const itParam = require('mocha-param').itParam;
const myData = [{ name: 'rob', age: 23 }, { name: 'sally', age: 29 }];
describe('test with array of data', () => {
itParam("test each person object in the array", myData, (person) => {
expect(person.age).to.be.greaterThan(20);
})
})
Take a look at async.each. It should enable you to call the same describe, it, and expect/should statements, and you can pass in the parameters to the closure.
var async = require('async')
var expect = require('expect.js')
async.each([1,2,3], function(itemNumber, callback) {
describe('Test # ' + itemNumber, function () {
it("should be a number", function (done) {
expect(itemNumber).to.be.a('number')
expect(itemNumber).to.be(itemNumber)
done()
});
});
callback()
});
gives me:
$ mocha test.js -R spec
Test # 1
✓ should be a number
Test # 2
✓ should be a number
Test # 3
✓ should be a number
3 tests complete (19 ms)
Here's a more complex example combining async.series and async.parallel: Node.js Mocha async test doesn't return from callbacks
Actually the mocha documentation specifies how to create what you want here
describe('add()', function() {
var tests = [
{args: [1, 2], expected: 3},
{args: [1, 2, 3], expected: 6},
{args: [1, 2, 3, 4], expected: 10}
];
tests.forEach(function(test) {
it('correctly adds ' + test.args.length + ' args', function() {
var res = add.apply(null, test.args);
assert.equal(res, test.expected);
});
});
});
The answer provided Jacob is correct just you need to define the variable first before iterating it.