Unit test case polling service in Angular - javascript

ngOnInit(): void {
this.timeInterval = interval(30000).pipe(startWith(0), switchMap(() => this.deviceService.getDeviceSession())
).subscribe((success: any) => {
this.rowData = success;
console.log(this.rowData)
}, (error: any) => {
this.rowData = [];
if (error.error && error.error.status !== 401) {
this.toastService.error('Error in loading data');
}
}
Test Case
it('should start checking for data after every interval', (done) => {
const dataService = TestBed.get(DeviceService);
// Mock the getStatus function
spyOn(dataService, 'getDeviceSession').and.returnValue(Observable.create().pipe(map(() => 'woo')));
// Should not be initialised yet
expect(component.rowData).toBeUndefined();
setTimeout(()=> {
expect(component.rowData).toBe('woo');
done();
},30000);
});
I am writing this unit test case to check this polling implementation, which is fetching data(array of objects) from service and updating it in the Dom. But the test case is getting failed. I am not sure where i am going wrong.
Getting error rowData.forEach is not a function. Please help how to solve it

Use Jasmin's beforeEach\before to initiate some data before it() instead of doing it in ngOnInit.
https://jasmine.github.io/api/3.5/global
So if im getting right what youre doing is, you initiate the rowData attribute in the ngOnInit life cycle hook. but it's not yet available inside of your it() function.
If you want to run some code and initiate data before you are runing your tests you should use the beforeEach() function and only then it()
check the image bellow:
https://miro.medium.com/max/4800/1*CklcdftSg9EFGsU20ZwdJg.png
A nice guide for doing unit tests with Angular and Jasmine:
https://medium.com/swlh/angular-unit-testing-jasmine-karma-step-by-step-e3376d110ab4

Related

Jest mocks bleeding between tests, reset isn't fixing it

Testing two modules, helper which makes use of render. It's possible for render to throw, so I handle that in helper, and I want tests to ensure that's working as expected.
When I originally wrote the tests, I wrote what was needed for that test in the test itself, including mocks, using jest.doMock. Once all the tests pass I wanted to refactor to share mocks where possible.
So this code works great:
test('throws', async () => {
jest.doMock('./render', () => jest.fn(async () => { throw new Error('mock error'); }));
const helper = require('./helper');
expect(async () => { helper(); }).rejects.toThrow('mock error');
expect(log_bug).toHaveBeenCalled();
});
test('succeeds', async () => {
jest.doMock('./render', () => jest.fn(async () => 'rendered result'));
const helper = require('./helper');
expect(await helper()).toEqual(true); //helper uses rendered result but doesn't return it
expect(log_bug).not.toHaveBeenCalled();
});
HOWEVER, these are not the only two tests and by far most of the other tests that mock render want it to return its success state. I tried to refactor that success use-case out to a file in __mocks__/render.js like so:
// __mocks__/render.js
module.exports = jest.fn(async () => 'rendered result');
And then refactor my tests to this, to be more DRY:
//intention: shared reusable "success" mock for render module
jest.mock('./render');
beforeEach(() => {
jest.resetModules();
jest.resetAllMocks();
});
test('throws', async () => {
//intention: overwrite the "good" render mock with one that throws
jest.doMock('./render', () => jest.fn(async () => { throw new Error('mock error'); }));
const helper = require('./helper');
expect(async () => { await helper(); }).rejects.toThrow('mock error');
expect(log_bug).toHaveBeenCalled();
});
test('succeeds', async () => {
//intention: go back to using the "good" render mock
const helper = require('./helper');
expect(await helper()).toEqual(true); //helper uses rendered result but doesn't return it
expect(log_bug).not.toHaveBeenCalled();
});
With this updated test code, the error-logging test still works as expected -- the mock is overwritten to cause it to throw -- but then for the next test, the error is thrown again.
If I reverse the order of these tests so that the mock overwriting is last, then the failure doesn't happen, but that is clearly not the correct answer.
What am I doing wrong? Why can't I get my mock to properly reset after overriding it with doMock? The doMock docs do kind of illustrate what I'm trying to do, but they don't show mixing it with normal manual mocks.
Aha! I kept digging around and found this somewhat similar Q+A, which led me to try this approach instead of using jest.doMock to override inside of a test:
//for this one test, overwrite the default mock to throw instead of succeed
const render = require('./render');
render.mockImplementation(async () => {
throw new Error('mock error');
});
And with this, the tests pass no matter what order they run!

How can I test a class which contains imported async methods in it?

This is my first time working with tests and I get the trick to test UI components. Now I am attempting to test a class which has some static methods in it. It contains parameters too.
See the class:
import UserInfoModel from '../models/UserInfo.model';
import ApiClient from './apiClient';
import ApiNormalizer from './apiNormalizer';
import Article from '../models/Article.model';
import Notification from '../models/Notification.model';
import Content from '../models/Link.model';
export interface ResponseData {
[key: string]: any;
}
export default class ApiService {
static makeApiCall(
url: string,
normalizeCallback: (d: ResponseData) => ResponseData | null,
callback: (d: any) => any
) {
return ApiClient.get(url)
.then(res => {
callback(normalizeCallback(res.data));
})
.catch(error => {
console.error(error);
});
}
static getProfile(callback: (a: UserInfoModel) => void) {
return ApiService.makeApiCall(`profile`, ApiNormalizer.normalizeProfile, callback);
}
}
I already created a small test which is passing but I am not really sure about what I am doing.
// #ts-ignore
import moxios from 'moxios';
import axios from 'axios';
import { baseURL } from './apiClient';
import { dummyUserInfo } from './../models/UserInfo.model';
describe('apiService', () => {
let axiosInstance: any;
beforeEach(() => {
axiosInstance = axios.create();
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
it('should perform get profile call', done => {
moxios.stubRequest(`${baseURL.DEV}profile`, {
status: 200,
response: {
_user: dummyUserInfo
}
});
axiosInstance
.get(`${baseURL.DEV}profile`)
.then((res: any) => {
expect(res.status).toEqual(200);
expect(res.data._user).toEqual(dummyUserInfo);
})
.finally(done);
});
});
I am using moxios to test the axios stuff -> https://github.com/axios/moxios
So which could be the proper way to test this class with its methods?
Introduction
Unit tests are automated tests written and run by software developers to ensure that a section of an application meets its design and behaves as intended. As if we are talking about object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method.
The goal of unit testing is to isolate each part of the program and show that the individual parts are correct. So if we consider your ApiService.makeApiCall function:
static makeApiCall(
url: string,
normalizeCallback: (d: ResponseData) => ResponseData | null,
callback: (d: any) => any
) {
return ApiClient.get(url)
.then((res: any) => {
callback(normalizeCallback(res.data));
})
.catch(error => {
console.error(error);
});
}
we can see that it has one external resource calling ApiClient.get which should be mocked. It's not entirely correct to mock the HTTP requests in this case because ApiService doesn't utilize them directly and in this case your unit becomes a bit more broad than it expected to be.
Mocking
Jest framework provides great mechanism of mocking and example of Omair Nabiel is correct. However, I prefer to not only stub a function with a predefined data but additionally to check that stubbed function was called an expected number of times (so use a real nature of mocks). So the full mock example would look as follows:
/**
* Importing `ApiClient` directly in order to reference it later
*/
import ApiClient from './apiClient';
/**
* Mocking `ApiClient` with some fake data provider
*/
const mockData = {};
jest.mock('./apiClient', function () {
return {
get: jest.fn((url: string) => {
return Promise.resolve({data: mockData});
})
}
});
This allows to add additional assertions to your test example:
it('should call api client method', () => {
ApiService.makeApiCall('test url', (data) => data, (res) => res);
/**
* Checking `ApiClient.get` to be called desired number of times
* with correct arguments
*/
expect(ApiClient.get).toBeCalledTimes(1);
expect(ApiClient.get).toBeCalledWith('test url');
});
Positive testing
So, as long as we figured out what and how to mock data let's find out what we should test. Good tests should cover two situations: Positive Testing - testing the system by giving the valid data and Negative Testing - testing the system by giving the Invalid data. In my humble opinion the third branch should be added - Boundary Testing - Test which focus on the boundary or limit conditions of the software being tested. Please, refer to this Glossary if you are interested in other types of tests.
The positive test flow flow for makeApiCall method should call normalizeCallback and callback methods consequently and we can write this test as follows (however, there is more than one way to skin a cat):
it('should call callbacks consequently', (done) => {
const firstCallback = jest.fn((data: any) => {
return data;
});
const secondCallback = jest.fn((data: any) => {
return data;
});
ApiService.makeApiCall('test url', firstCallback, secondCallback)
.then(() => {
expect(firstCallback).toBeCalledTimes(1);
expect(firstCallback).toBeCalledWith(mockData);
expect(secondCallback).toBeCalledTimes(1);
expect(secondCallback).toBeCalledWith(firstCallback(mockData));
done();
});
});
Please, pay attention to several things in this test:
- I'm using done callback to let jest know the test was finished because of asynchronous nature of this test
- I'm using mockData variable which the data that ApiClient.get is mocked this so I check that callback got correct value
- mockData and similar variables should start from mock. Otherwise Jest will not allow to out it out of mock scope
Negative testing
The negative way for test looks pretty similar. ApiClient.get method should throw and error and ApiService should handle it and put into a console. Additionaly I'm checking that none of callbacks was called.
import ApiService from './api.service';
const mockError = {message: 'Smth Bad Happened'};
jest.mock('./apiClient', function () {
return {
get: jest.fn().mockImplementation((url: string) => {
console.log('error result');
return Promise.reject(mockError);
})
}
});
describe( 't1', () => {
it('should handle error', (done) => {
console.error = jest.fn();
const firstCallback = jest.fn((data: any) => {
return data;
});
const secondCallback = jest.fn((data: any) => {
return data;
});
ApiService.makeApiCall('test url', firstCallback, secondCallback)
.then(() => {
expect(firstCallback).toBeCalledTimes(0);
expect(secondCallback).toBeCalledTimes(0);
expect(console.error).toBeCalledTimes(1);
expect(console.error).toBeCalledWith(mockError);
done();
});
});
});
Boundary testing
Boundary testing could be arguing in your case but as long as (according to your types definition normalizeCallback: (d: ResponseData) => ResponseData | null) first callback can return null it could be a good practice to check if is the successfully transferred to a second callback without any errors or exceptions. We can just rewrite our second test a bit:
it('should call callbacks consequently', (done) => {
const firstCallback = jest.fn((data: any) => {
return null;
});
const secondCallback = jest.fn((data: any) => {
return data;
});
ApiService.makeApiCall('test url', firstCallback, secondCallback)
.then(() => {
expect(firstCallback).toBeCalledTimes(1);
expect(firstCallback).toBeCalledWith(mockData);
expect(secondCallback).toBeCalledTimes(1);
done();
});
});
Testing asynchronous code
Regarding testing asynchronous code you can read a comprehensive documentation here. The main idea is when you have code that runs asynchronously, Jest needs to know when the code it is testing has completed, before it can move on to another test. Jest provides three ways how you can do this:
By means of a callback
it('the data is peanut butter', done => {
function callback(data) {
expect(data).toBe('peanut butter');
done();
}
fetchData(callback);
});
Jest will wait until the done callback is called before finishing the test. If done() is never called, the test will fail, which is what you want to happen.
By means of promises
If your code uses promises, there is a simpler way to handle asynchronous tests. Just return a promise from your test, and Jest will wait for that promise to resolve. If the promise is rejected, the test will automatically fail.
async/await syntax
You can use async and await in your tests. To write an async test, just use the async keyword in front of the function passed to test.
it('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
Example
Here you can find a ready to use example of your code
https://github.com/SergeyMell/jest-experiments
Please, let me know if something left unclear for you.
UPDATE (29.08.2019)
Regarding your question
Hi, what can I do to mock ./apiClient for success and error in the same file?
According to the documentation Jest will automatically hoist jest.mock calls to the top of the module (before any imports). It seems that you can do setMock or doMock instead, however, there are issues with mocking this way that developers face from time to time. They can be overridden by using require instead of import and other hacks (see this article) however I don't like this way.
The correct way for me in this case is do split mock defining and implementation, so you state that this module will be mocked like this
jest.mock('./apiClient', function () {
return {
get: jest.fn()
}
});
But the implementation of the mocking function differs depending on scope of tests:
describe('api service success flow', () => {
beforeAll(() => {
//#ts-ignore
ApiClient.get.mockImplementation((url: string) => {
return Promise.resolve({data: mockData});
})
});
...
});
describe('api service error flow', () => {
beforeAll(() => {
//#ts-ignore
ApiClient.get.mockImplementation((url: string) => {
console.log('error result');
return Promise.reject(mockError);
})
});
...
});
This will allow you to store all the api service related flows in a single file which is what you expected as far as I understand.
I've updated my github example with api.spec.ts which implements all mentioned above. Please, take a look.
The unit test term is self-explanatory that you test a unit. A function in complete isolation. Any outside dependencies are mocked. Here if your'e testing makeApiCall function you'll have to stub it's parameters and then mock the ApiClient promise and expect the function to return whatever you're expecting it to return with respect to your mocked and stub parameters.
One thing that people normally forget and which is the most important is to test the negative cases of a function. What will happen if your function throws an error will it break the app. How your function behaves in case something fails. Tests are written to avoid breaking changes in the app.
here is a better guide how to test async functions in JEST which coding examples:
https://www.leighhalliday.com/mocking-axios-in-jest-testing-async-functions
Hope this helps
UPDATE
Mock your ApiClient
for pass case:
jest.mock('./apiClient', () => {
get: jest.fn(() => Promise.resolve(data)) // for pass case
})
for fail case:
jest.mock('./apiClient', () => {
get: jest.fn(() => Promise.reject(false)) // for fail case
})
now call your makeApiCall for both cases once for success and once for fail.
for fail case:
const makeCall = await makeApiCall( <your stub params here> )
expect(makeCall).toThrowError() // note here you can check whatever you have done to handle error. ToThrowError is not a built-in function but just for understanding
I've mostly done testing in Jasmine so this last piece of code is kind of a psuedo code.
I guess what you are asking is how to test ApiService. If this is the case, then mocking the very own thing you want to test would make the unit test pointless.
What I would expect is the following items
You just want to test logic in your own class, not in the library.
You don't want to make an actual network request, this spams the server and make the test slower to run.
If this is the case, then you should mock out some lib to control their behaviour and see how your class behave under those circumstances. And, mock out any operation that involves network IO, make your test faster and less reliant on external resources.
There are a few things you could check with some dependencies mocked out:
delegation, e.g. is axios called once, with the right param?
directly mock the behaviour of the lib, in your case using maxios.
import ApiService, { baseURL } from './apiClient';
describe('ApiService', () => {
let axiosInstance: any;
beforeEach(() => {
axiosInstance = axios.create();
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
// usually 1 test suite for each method
describe('#getProfile', (done) => {
// mocking behaviour
it('should perform get profile call', () => {
moxios.stubRequest(`${baseURL.DEV}profile`, {
status: 200,
response: {
_user: dummyUserInfo
}
});
ApiService.getProfile((profile) => {
expect(profile).toEqual(dummyUserInfo); // you get what i mean
done();
});
});
// directly mock axios
it('delegates to axios', (done) => {
// you should put this to the top to avoid confusion, it will be hoisted
jest.mock('axios', () => ({
create: jest.fn(() => ({
get: jest.fn(() => Promise.resolve()),
})),
}));
ApiService.getProfile((profile) => {
// do some assertion
expect(axiosInstance.get).toHaveBeenCalledTimes(1);
expect(axiosInstance.get).toHaveBeenCalledWith(url, someParam, youGetIt);
done();
});
});
// rmb to test some error case
it('should throw when param is not correct', (done) => { ... });
});
});

Test that an Angular observable emits a value or sequence

I have a service that creates an observable that emits values, and it's relatively easy to test that an emitted value is as expected.
For example:
describe('service', () => {
beforeEach(() => {
TestBed.configureTestingModule({providers: [MyService]});
});
it('should emit true', async(() => {
const service = TestBed.get(MyService);
service.values$.subscribe((value) => expect(value).toBeTruthy());
}));
});
The above works to test the expected value, but it only works if the service actually emits a value. If you have the edge case where the service fails to emit a value, then the test itself actually passes and Jasmine logs the warning message "SPEC HAS NO EXPECTATIONS should be created".
I searched Google for a while trying to figure out how to catch this case as an error and came up with this approach.
it('should emit true', async(() => {
const service = TestBed.get(MyService);
let value;
service.values$.subscribe((v) => value = v);
expect(value).toBeTruthy();
}));
The above works only for synchronous observables and feels like code smell to me. Another developer will see this and think it's a poor quality test.
So after thinking about this for a few days. I thought of using takeUntil() to force the observable to complete and then test the expected result then.
For example:
describe('service', () => {
let finished: Subject<void>;
beforeEach(() => {
TestBed.configureTestingModule({providers: [MyService]});
finished = new Subject();
});
afterEach(() => {
finished.next();
finished.complete();
});
it('should emit true', async(() => {
const service = TestBed.get(MyService);
let value;
service.changes$
.pipe(
takeUntil(finished),
finalize(() => expect(value).toBeTruthy())
)
.subscribe((v) => value = v);
}));
});
In the above example the value is being stored in a local variable and then the expected result is checked when the observable completes. I force the completion by using afterEach() with takeUntil().
Question:
Are there any side effects with my approach, and if so what would be the more Angular/Jasmine way of performing these kinds of tests. I am worried that you are not suppose to perform expect assertions during the afterEach() life-cycle call.
This seems overkill to me.
Jasmine offers a callback in its tests, you could simply use it ?
it('should X', doneCallback => {
myObs.subscribe(res => {
expect(x).toBe(y);
doneCallback();
});
});
If the callback isn't called, the test fails with a timeout exception (meaning no more test will run after this failed one)

Correct way to unit-test observable stream being fed

I use this to test that the service adds an item to observable stream of errors.
it('can be subscribed for errors', () => {
let testError = new Error('Some error.');
let called = false;
let subscription = service.onError.subscribe(error => {
called = true;
expect(error).toEqual(testError);
});
/// This makes an error to be added to onError stream
service.setError(testError);
expect(called).toEqual(true);
});
I use the called variable to make sure that the subscription callback was actually called. Otherwise the test would pass when it shouldn't. But it doesn't seem right to me. Also, it wouldn't work if the stream was asynchronous.
Is this a good way to test that? If not, how to do it properly?
EDIT: this is the class that's being tested. It's in typescript, actually.
import { ReplaySubject } from 'rxjs/Rx';
export class ErrorService {
private error: Error;
public onError: ReplaySubject<Error> = new ReplaySubject<Error>();
constructor() {
}
public setError = (error: Error) => {
this.error = error;
console.error(error);
this.onError.next(error);
}
public getError() {
return this.error;
}
public hasError() {
return !!this.error;
}
}
The way you are testing is good. You are:
Checking if the value is correct with expect statement.
Checking the fact the expect statement is being executed.
Especially the last part is important otherwise the expect might not be triggered and the test will falsely pass.

$httpBackend doesn't seem to be flushing requests

I am testing my Angular app using ngDescribe. I don't think ngDescribe should be too much of a problem here, as it's just managing dependency injection for me. I first began to attempt my test the way the ngDescribe docs say, in the code below I have changed it to a more direct approach just to see if I could get any changes. I am calling a method that in turn calls $http.post('urlname', data); While debugging I can clearly see that my method gets all the way to where it calls post() but then it never continues. Because of this, my test always times out.
Hopefully I've just got something simple that's wrong! Here is my code. The test that fails is "Should work", all the others pass as expected.
Please also note that this is being processed by babel, both the test and the service, before being tested.
Here is the service, it works perfectly when being used. It has a few other variables involved that I have removed, but I assure you those variables are working correctly. While debugging for the tests, I can see that the await is hit, but it never continues past that, or returns. When used in the app, it returns exactly as expected. I THINK this has something to do with ngmock not returning as it should.
async function apiCall (endPoint, data) {
if (!endPoint) {
return false;
}
try {
return data ? await $http.post(`${endPoint}`, data) : await $http.get(`${endPoint}`);
} catch (error) {
return false;
}
}
Here are the tests:
ngDescribe({
name: 'Api Service, apiCall',
modules: 'api',
inject: ['apiService', '$httpBackend'],
tests (deps) {
let svc;
beforeEach(() => {
svc = deps.apiService;
});
it('is a function', () => {
expect(angular.isFunction(svc.apiCall)).toBe(true);
});
it('returns a promise', () => {
const apiCall = svc.apiCall();
expect(angular.isFunction(apiCall.then)).toBe(true);
});
it('requires an endpoint', async () => {
const apiCall = await svc.apiCall();
expect(apiCall).toBe(false);
});
it('should work', (done) => {
deps.http.expectPOST('fakeForShouldWork').respond({ success: true });
const apiCall = svc.apiCall('fakeForShouldWork', {});
apiCall.then(() => done()).catch(() => done());
deps.http.flush();
});
},
});
The method being called, apiCall, is simply a promise that is resolved by $http.post().then(); It will also resolve false if an error is thrown.
Since deps.http.expectPOST does not fail, I can tell that the outgoing request is sent. I validated this by changing it to expectGET and then I received an error about it being a POST.
I have tried moving the flush() method to all different parts of the test method, but it seems to make no difference.
Any thoughts? Thanks so much for your help!

Categories

Resources