Testing: spyOn Helper class in angular - javascript

Is it possible to spyOn helper class? In the below code, StatefulPatternService.init() is calling a WebSocketHelper.
I would like to spyOn WebSocketHelper and mock the subscribeFn
export class WebSocketHelper{
private url: string;
constructor(url){
this.url = url;
init();
}
init(){
// init websocket & other login
}
}
#Injectable({
providedIn: 'root'
})
export class StatefulPatternService {
constructor(){}
private callback(_data){ }
init(){
let wsHelper = new WebSocketHelper('/subscribe/topic'); // <-- How to spyOn???
wsHelper.subscribeFn = this.callback;
// ...
}
}
If spyOn won't be possible, then how it can be re-written so that this test can be covered?

Your challenge will be getting a hold of 'wsHelper' in order to spy on it. One thought: can you refactor to make wsHelper a class-scope variable instead? Then you could spyOn it when you get the service in the test suite, for example, something like:
service = TestBed.get(StatefulPatternService);
let spy = spyOn(service.wsHelper, 'subscribeFn');
Update
From the comments to my answer it looks like what you are really trying to do is verify that the constructor was called with the proper url. Since you are saving that in a class variable, there should be no need to spy on the constructor, but rather just test the value of the saved variable. As I mentioned in the comments, to do this you will need two things: to make wsHelper a class level variable, and to add a method on the WebSocketHelper class that returns the value of the private variable 'url' so you can test it. I've set up a stackblitz to demonstrate what I'm talking about here: STACKBLITZ Here is a snippet from that stackblitz:
describe('MyService', () => {
let myService: StatefulPatternService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [/* any imports needed */],
providers: [ StatefulPatternService ]
});
myService = TestBed.get(StatefulPatternService);
});
it('should be createable', () => {
expect(myService).toBeTruthy();
});
it('should construct wsHelper properly', () => {
myService.init();
expect(myService.wsHelper.get_url()).toEqual('/subscribe/topic');
})
});

Related

Angular 9 unit test external function referring property inside service gives errors

I have a service to run unit tests as follows. It has two helper functions which use service params as follows.
Here is the service.ts file
export function storeToStorage<T>(key: string, item: T): void {
this.storageService.libLocalStorage( 'set' ,key, item )
}
export function getFromStorage<T>(key: string): T {
return this.storageService.libLocalStorage( 'get' ,key )
}
#Injectable({
providedIn: 'root'
})
export class TableColumnService {
...
constructor(private storageService: LocalStorageService) {
localStorage.removeItem('KEY');
this._initializeColumns();
}
...
}
Here Local storage service is service implemented separately for managing storage of the application. And application is running with out any error.
My implementation for service.spec.ts as follows
describe('TableColumnService', () => {
let service: TableColumnService;
let localStorageService: LocalStorageService;
beforeEach(async (() => {
TestBed.configureTestingModule({
imports: [TestSharedModule],
providers: [LocalStorageService]
});
service = TestBed.inject(TableColumnService);
localStorageService = TestBed.inject(LocalStorageService);
}));
it('should be created', () => {
expect(service).toBeTruthy();
});
});
I am getting following error when running
Chrome Headless 90.0.4430.212 (Mac OS 10.15.7) TableColumnService should be created FAILED
Failed: Cannot read property 'storageService' of undefined
at <Jasmine>
I tried to spy external functions as follows. But giving errors
beforeEach(async (() => {
TestBed.configureTestingModule({
imports: [TestSharedModule],
providers: [LocalStorageService]
});
service = TestBed.inject(TableColumnService);
localStorageService = TestBed.inject(LocalStorageService);
spyOn(storeToStorage, 'storeToStorage').and.returnValue({});
spyOn(getFromStorage, 'getFromStorage').and.returnValue({});
}));
It gives following error on spy functions
Argument of type '{}' is not assignable to parameter of type 'never'
I reckon you're not spying correctly; it should be something like below and yes, you have to make stroage service public in ts file. If you don't want to make it public you can also create a stub for this service in your spec file.
spyOn(service.storageService, 'storeToStorage').and.returnValue({});

Mock method from class 'b' that is called in class 'a'

I'm trying to test a method in the class test.controller.ts, this method also happens to call a method from a different class that is being tested seperately, so I want to mock that call.
Here is an example setup to show what i'm trying to do.
TestController.test.ts
import {TestController} from "../../src/controllers/test.controller";
import {TestService} from "../../src/services/test.service";
describe("Test TestController", () => {
test("exampleController", () => {
jest.mock('../../src/services/test.service');
let testService = new TestService();
let testController = new TestController();
// Mock testService.exampleService so it returns 2.
let result = testController.exampleController();
expect(result).resolves.toBe(2);
});
});
test.controller.ts
import {TestService} from "../services/test.service";
export class TestController {
private testService: TestService;
constructor() {
this.testService = new TestService();
}
public async exampleController() {
return await this.testService.exampleService();
}
}
test.service.ts
export class TestService {
public async exampleService() {
return 1;
}
}
How do I mock the method 'exampleService' so that the call to the method 'exampleController' from test.controller.ts uses this mocked version?
You need to mock the testService field of your TestController class.
But with your current code, that isn't possible as it's a private member.
This is why using dependency injection is preferred, so we need to change your constructor to this,
constructor(testService: TestService) {
this.testService = testService;
}
Instead of instantiating testService within the constructor, we are now passing an object of TestService so that it is easy to mock.
And then you can test it like this,
import {TestController} from "./controller";
import {TestService} from "./service";
jest.mock('./service.ts')
describe("Test TestController", () => {
test("exampleController", async () => {
let testService = new TestService();
jest.spyOn(testService, 'exampleService').mockResolvedValue(2)
let testController = new TestController(testService);
let result = await testController.exampleController();
expect(result).toBe(2);
});
});
Here you create an object of TestService.
Then you create a spy on the exampleService method of the testService object and mock its resolved value to return 2.
Then you pass it to TestController's constructor, this is called dependency injection, which makes it easier to test.
And then you proceed to assert as per your expectations.
For mocking functions you can use the sinon library. It can be done as shown below:
let testService = new TestService();
sinon.stub(testService, "exampleService").callsFake(function fakeFn() {
return 2;
});
If you need to specify the return values and full-on replace the implementation of a function with a mock function, it can be done with jest.fn and mockImplementationOnce method on mock functions.
TestController.test.ts should look like
import {TestController} from "../../src/controllers/test.controller";
import {TestService} from "../../src/services/test.service";
describe("Test TestController", () => {
test("exampleController", () => {
jest.mock('../../src/services/test.service');
let testService = new TestService();
let testController = new TestController();
// Mock testService.exampleService so it returns 2.
testService.exampleService = jest.fn().mockImplementationOnce(() => Promise.resolve(2));
let result = testController.exampleController();
expect(result).resolves.toBe(2);
});
});

How to mock document,hidden with Jasmine?

I am trying to mock document.hidden in angular unit test, but it is not working.
I already have tried these options:
spyOn(Document.prototype, <any>'hidden').and.returnValue(true);
spyOn(Document, <any>'hidden').and.returnValue(true);
spyOn(document, <any>'hidden').and.returnValue(true);
spyOn(document, <any>'hidden').and.callFake(() => true);
spyOn(DOCUMENT, <any>'hidden').and.returnValue(true); // using TestBed
Thanks in advance
spies are made for functions, not for just property values. to mock a property just do
document.hidden = true;
update: because hidden is a readonly property I would suggest to inject document object to the component, and then provide it with any value you want inside of test
class MyComponent {
constructor(#Inject(DOCUMENT) private document: Document) {}
...
}
// test
let documentMock: any;
...
documentMock = {hidden: true};
TestBed.configureTestingModule({
providers: [{provide: DOCUMENT, useValue: documentMock}]
})

Can I use Angular2 service in DOM addEventListener?

Issue: When I try to call service within addEventListener, service seems to be empty.
Html:
<div id="_file0">
Service:
#Injectable()
export class FilesService {
constructor(private http : Http) { }
}
Component:
export class NoteComponent implements OnInit {
constructor(private filesService : FilesService) { }
ngOnInit() {
this.addEvent(document.getElementById("_file0"));
}
addEvent(div){
console.log("tag1", this.filesService);
div.addEventListener("click", function(){
console.log("tag2", this.filesService);
});
}
}
Console output:
tag1 FilesService {http: Http}
tag2 undefined
Try using this code to maintain this context
div.addEventListener("click", () => {
console.log("tag2", this.filesService);
});
Additional info: this is called Arrow function or lamda function. What it actually does is when Typescript compiles your code into javascript, it creates another _this variable to store the real this and whenever you call this it gets replaces by _this.
That's why your original code gets compile, this is undefined because it was the context of click event not your Angular component.

Unit tests for firebase add using angular 2

Does anyone know how to do a basic unit test using angular 2 to test a basic firebase add item.
I'm using typescript instead of basic JavaScript for my code
This is what I'm testing:
export class AppComponent {
ref: Firebase;
refUsers: Firebase;
refProfiles: Firebase;
constructor(){
this.ref = new Firebase("https://markng2.firebaseio.com");
this.refUsers = new Firebase("https://markng2.firebaseio.com/users");
this.refProfiles = new Firebase("https://markng2.firebaseio.com/profiles");
}
public addUser(newUser: Object): void{
this.refUsers.push(newUser, ()=>{
});
}
}
This is my current test:
import {it, iit, describe, expect, inject, injectAsync, beforeEachProviders, fakeAsync, tick } from 'angular2/testing';
import { AppComponent } from '../app/app';
describe('AppComponent', () => {
it('saves an item to Firebase', () => {
let refUsers = new Firebase('');
let service = new AppComponent();
spyOn(service.refUsers, 'push');
service.addUser({ item: true });
expect(service.refUsers.push).toHaveBeenCalled();
})
});
This is the error I'm getting when I run that test:
Three steps to begin testing.
Setup your testing environment. The Angular 2 docs have a great guide on doing so.
Write your code.
Write the test.
Let's say you create a class called DataService:
/// <reference path="typings/firebase/firebase.d.ts" />
export class DataService {
ref: Firebase;
constructor(theRef: Firebase) {
this.ref = theRef;
}
add(object: any) {
this.ref.push(object);
}
}
To test it, you can import DataService and use Jasmine methods to test that the add method.
import {DataService} from './data-service';
describe('DataService', () => {
it('saves an item to Firebase', () => {
let ref = new Firebase('');
let service = new DataService(ref);
// create a spy to use if push is called
spyOn(service.ref, 'push');
service.add({ item: true });
// expect that push was called
expect(service.ref.push).toHaveBeenCalled();
})
});
The key to testing Firebase methods is just to spy on them. You don't need to test that Firebase works, just that your code calls Firebase properly.
The problem here is that you're using the full Firebase SDK in your unit tests. Ideally you'd want to use a mocked library, so you can create a mock for whatever functionality you need from the Firebase SDK.

Categories

Resources