Testing an rxjs get method(service), on the component - javascript

The function call on the service is like so.
get getLayerToEdit(): BehaviorSubject<VectorLayer> {
return this.layerToEdit;
}
Which is then invoked on the ngOnInit like so.
ngOnInit() {
this.annoService.getLayerToEdit.subscribe((layerToEdit: any) => {
this.layer = layerToEdit;
this.layerId = layerToEdit.ol_uid;
});
So naturally in my test I'm wanting to see if the component.layer matches what this service returns.
Test File
let component: EditFeatureComponent;
let fixture: ComponentFixture<EditFeatureComponent>;
let mockAnnoService = jasmine.createSpyObj(['getLayerToEdit', 'getCurrentAction', 'setCurrentAction']);
let layer;
The first before each code block
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [EditFeatureComponent],
imports: [FormsModule, HttpClientModule],
providers: [
{ provide: AnnotationService, useValue: mockAnnoService}
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
// Fields required for the component
layer = {
layerName: 'testLayer',
ol_uid: 1
}
// Create the testbed
fixture = TestBed.createComponent(EditFeatureComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
And then the actual test
it('Should return a layer object and have a ol_uid of 1', ()=>{
mockAnnoService.getLayerToEdit.and.returnValue(of(layer))
fixture.detectChanges();
expect(component.layer).toBe(layer);
})
The test returns 'this.annoService.getLayerToEdit.subscribe is not a function'
I've also tried using subscribe rather than of.
mockAnnoService.getLayerToEdit.and.returnValue({ subscribe: () => {} })
I'm pretty ill informed on testing but this is the approach I'd normally take to test services but it's my first time testing a 'get' method from a service so I'm assuming that I'm going wrong somewhere.

Your mockAnnoService.getLayerToEdit is getter so you cannot mock it using jasmine.createSpyObj because it mocks only methods in that way.
You need to create plain object instead of mock and assign observable with needed data to the getLayerToEdit field.
Hope that helps.

Related

Testing an observable on a service property using jasmine.SpyObj - Angular/Jasmine

I'm trying to test an observable that is bound to an inline property rather than a method on a service, however I can't seem to find any examples about how to return the correct value either in the SpyObj itself or through a spyOn/SpyOnProperty call.
// ContentService
readonly content$ = this.http.get('www.testContent.com');
// ParentService
constructor(private readonly contentService: ContentService){
this.loadContent();
}
// This could return different content dependent on locale for example
loadContent(){
this.contentService.content$.subscribe((response)=>{
// Handle response
})
}
// Unit test
let service: ParentService;
let contentServiceSpy: jasmine.SpyObj<ContentService>;
beforeEach(() => {
const spy = jasmine.createSpyObj('ContentService', [], {
// Returns cannot read subscribe of undefined
content$: of()
});
TestBed.configureTestingModule({
providers: [
ParentService,
{ provide: ContentService, useValue: spy }
]
});
service = TestBed.get(ParentService);
contentServiceSpy = TestBed.get(ContentService) as jasmine.SpyObj<ContentService>;
});
I've seen a couple of examples about using spyOnProperty with get and set methods but that's not what's happening here, am I declaring it incorrectly on the jasmine.SpyObj or is there a particular jasmine method that I'm missing to return the desired value from the content$
What I do in these scenarios is I add an instance property to the createSpyObj itself.
// Unit test
let service: ParentService;
let contentServiceSpy: jasmine.SpyObj<ContentService>;
let mockContent$ = new BehaviorSubject(/* content mock goes here*/);
beforeEach(() => {
const spy = jasmine.createSpyObj('ContentService', ['loadContent']);
spy.content$ = mockContent$; // add the instance property itself
TestBed.configureTestingModule({
providers: [
ParentService,
{ provide: ValueService, useValue: spy }
]
});
service = TestBed.get(ParentService);
contentServiceSpy = TestBed.get(ContentService) as jasmine.SpyObj<ContentService>;
});
Later on, if you want to change the value of content$, you can do mockContent$.next(/* new mock content goes here*/);

jasmine unit testing a component which has data in a services, where that service fetches data from other service in angular 2+

I have tried to write unit testing of a httpmock one of a service which uses a method (function) to get http get call, but failed to write.
saveservice.service.ts -- file
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
const envURL = sessionStorage.getItem('apiBaseURL');
httpGet<T>(url) {
const URL = envURL + url;
return this.http.get<T>(URL, httpOptions);
}
this saveservice.service file have httpGET() one, which is used by work.service.ts one
work.service.ts
import {SaveserviceService } from '../../.././my-service.service';
getworklist(employeeID){
return this.saveservice.httpGet('work/v1/works?employeeid=' + employeeID);
}
this is how workservice and save service are connected.Now I want to write unit test case for work.component.ts file,but Iam unable to write the httpmock calls there .
For info, apiUrl is present in a different file named env.ts file--
env.ts
export const apivalue= {
apiBaseUrl:"https://example.co/",
};
work.component.ts
ngOnit(){
this.employeeID:this.id;
this.workservice.getworkList(this.employeeID).subscribe(
(data) => {
this.workList = data;
console.log(" ggghfghfgh", this.worklist);
}, (error) => {
console.log(error);}
Above one is work.component.ts file for which i should write unit test cases.Please help me to complete it.
work.component.spec.ts
let httpMock: HttpTestingController;
let injector: Injector;
let workservice: WorksService;
let saveservice123: SaveService;
providers: [
Injector,
HttpClient,
HttpClientTestingModule,
saveService,
worksService
],
httpMock = TestBed.get(HttpTestingController);
workservice = TestBed.get(WorksService);
saveservice123 = TestBed.get(SaveService)
fit('getting work detsails indivually', async(() => {
fixture = TestBed.createComponent(worksComponent);
component = fixture.componentInstance;
fixture.detectChanges();
workservice.getworkList(123).subscribe(() =>{});
const request = httpMock.expectOne("work/v1/works?employeeid=")
// expect(request.request.method).toBe('httpGet');
// request.flush(xxxxx);
its throwing error
Error: Expected one matching request for criteria "Match URL: work/v1/works?employeeGuid=", found none.
Please help , how to know to write unit test cases for this type..
I also tried with spy also,but unable to complete.
const mockdata = { id:1, title: "Hello world", paste: "console.log('Hello world');"}
const spyOnAdd = spyOn(service, "getworkList").and.returnValue(mockdata);
First make sure your component implements the interface OnInit and its method ngOnint is renamed to ngOnInit.
Your component unit test should not care about how WorkService is implemented but entirely concentrate on the behavior of the component (the unit) itself under specified conditions. Therefore you must mock relevant part of the WorkService.
Assuming SaveService is the only parameter provided to the WorkService constructor, the simplest implementation of work.component.spec.ts could look as follows.
import { ComponentFixture, TestBed, fakeAsync, tick } from '#angular/core/testing';
...
describe('WorkService', () => {
const workList = [...]; // specify result of WorkService#getworklist
const workService = new WorkService(null);
let fixture: ComponentFixture<WorkComponent>;
beforeEach(fakeAsync(() => {
spyOn(workService , 'getworklist').and.returnValue(Promise.resolve(workList));
TestBed.configureTestingModule({
imports: [...],
declarations: [WorkComponent],
providers: [
{ provide: WorkService, useValue: workService }
]
});
fixture = TestBed.createComponent(WorkComponent);
component = fixture.componentInstance;
fixture.detectChanges();
tick();
}));
it('getting work details individually', () => {
expect(workService.getworklist).toHaveBeenCalled();
expect(component.workList).toEqual(workList);
});
});

Angular/Rxjs get sync data

Whether there method getting data sync from http.get ? I want use data inside component in order to set router.resetConfig().
Data from http.get = [{path: 'site/a', component: Component}, {path: 'site/b', component: Component}...].
I tried to use BehaviorSubject, next(), switchMap but always I must to use .subscribe().
class A {
data = new BehaviorSubject<any>();
url = 'url';
constructor() {
this.getUrl();
console.log(this.data.value);
}
getUrl() {
this.http.get(this.url).pipe(
map(result => {
this.data.next(result)
}));
}
}
If I use subscribe()
this.http.get(this.url).pipe(map(result => result)).subscribe(data => {this.data.next(data)});
If console.log(this.data) value exist, console.log(this.data.value) not exist.
I want to get:
router.resetConfig(this.data.value);
I think you're looking for APP_INITIALIZER. I can not found a good example but basically you defined a function in a service that return a promise
//in your service
loadData() {
return this.httpClient.get(...)
.toPromise()
.then(res=>{
this.data=res //<--here you store the response
}
in your module.ts
export function init_app(myService: MyService) {
return () => myService.loadData();
}
#NgModule({
declarations: [...],
imports: [...],
providers: [
...,
{ provide: APP_INITIALIZER, useFactory: init_app, deps: [MyService], multi: true }
],
bootstrap: [AppComponent]
})
So, you can use myService.data in any component that inject the service because the first time Angular get the data
The point is to download synchronized data, router.resetConfig({must be sync data}).
I was thinking about emitting the last value and saving it somehow. I'm trying to use .subscribeOn(), .observeOn() and Scheduler.
The data you get from your http.get is an array. You don't need to modify any data in the array instead you just want to pass the data as is. So what you need to do instead is just pass an observable from your getUrl function and subscribe to it where you want to get the data. Since http.get returns an Observable, you can just return your http.get in getUrl.
I would also suggest you move the logic from your constructor to your ngOnInit as that's the best practice. (Source: docs)
import { Router, Routes } from '#angular/router';
class A {
url = 'url';
constructor(
private router: Router
) { }
ngOnInit() {
this.getUrl().subscribe((data: Routes) => {
console.log(data);
this.router.resetConfig(data);
});
}
getUrl(): Observable<Routes> {
return this.http.get(this.url);
}
}
It would also be suggested to move all your http calls to a service. (Source: docs)
Here is a working example on StackBlitz.

How to reset a spy in Jasmine?

I have an issue where I've set up a mock service as a spy.
mockSelectionsService = jasmine.createSpyObj(['updateSelections']);
I then call that stub method twice, each time in a different test. The problem is that when i expect() the spy with .toHaveBeenCalledWith() the toHaveBeenCalledWith method also contains the arguments it was passed from the first test which produces a false positive on my second test.
How do I wipe/clear/reset the spyObject for my next test so that it no longer believes it as been called at all?
Initialisation of services/components
beforeEach(() => {
mockSelectionsService = jasmine.createSpyObj(['updateSelections']);
TestBed.configureTestingModule({
declarations: [QuickSearchComponent, LoaderComponent, SearchComponent, SearchPipe, OrderByPipe],
providers: [OrderByPipe, SearchPipe, SlicePipe, {provide: SelectionsService, useValue: mockSelectionsService}],
imports: [FormsModule, HttpClientModule]
});
fixture = TestBed.createComponent(QuickSearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
fixture.componentInstance.templates = mockTemplates;
fixture.componentInstance.manufacturers = mockManufacturers;
});
const spy = spyOn(somethingService, "doSomething");
spy.calls.reset();
This resets the already made calls to the spy. This way you can reuse the spy between tests. The other way would be to nest the tests in another describe() and put a beforeEach() in it too.
Type 1:
var myService = jasmine.createSpyObj('MyService', ['updateSelections']);
myService.updateSelections.calls.reset();
Type 2:
var myService = spyOn(MyService, 'updateSelections');
myService.updateSelections.calls.reset();
Note: Code above is tested on Jasmine 3.5
The mock service with the default return value can be set in the beforeEach() , but if you want to change the mocked service response later, do not call fixture.detectChanges() in beforEach, you can call it in each spec after applying required changes (if any), if you want to change the mock service response in an specific spec, then add it to that spec before the fixture.detectChanges()
beforeEach(() => {
serviceSpy = jasmine.createSpyObj('RealService', ['methodName']);
serviceSpy.methodName.and.returnValue(defaultResponse);
TestBed.configureTestingModule({
providers: [{provide: RealService, useValue: serviceSpy }],
...
})
...
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
// do not call fixture.detectChanges() here
});
it('test something with default mocked service response', () => {
fixture.detectChanges();
....
});
it('test something with a new mocked service response', () => {
serviceSpy.methodName.and.returnValue(newResponse);
fixture.detectChanges();
....
});
I use it like this:
let service: anyTypeYouWant;
let spyOnMethod: jasmine.Spy<(stringInput: string) => string>;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(..Your_Inject..);
spyOnMethod= spyOn(service, "myMethod");
});
it('Your Describe', () => {
expect(service.myMethod).toHaveBeenCalled();
})
add to Jelle's answer, if you got Cannot read property 'reset' of undefined when attempting mockSelectionsService.calls.reset(), try:
const spy = spyOn(somethingService, "doSomething");
spy.mockRestore();
reference: jest spyon demo: https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname

How to stub a method of jasmine mock object?

According to the Jasmine documentation, a mock can be created like this:
jasmine.createSpyObj(someObject, ['method1', 'method2', ... ]);
How do you stub one of these methods? For example, if you want to test what happens when a method throws an exception, how would you do that?
You have to chain method1, method2 as EricG commented, but not with andCallThrough() (or and.callThrough() in version 2.0). It will delegate to real implementation.
In this case you need to chain with and.callFake() and pass the function you want to be called (can throw exception or whatever you want):
var someObject = jasmine.createSpyObj('someObject', [ 'method1', 'method2' ]);
someObject.method1.and.callFake(function() {
throw 'an-exception';
});
And then you can verify:
expect(yourFncCallingMethod1).toThrow('an-exception');
If you are using Typescript, it's helpful to cast the method as Jasmine.Spy. In the above Answer (oddly I don't have rep for comment):
(someObject.method1 as Jasmine.Spy).and.callFake(function() {
throw 'an-exception';
});
I don't know if I'm over-engineering, because I lack the knowledge...
For Typescript, I want:
Intellisense from the underlying type
The ability to mock just the methods used in a function
I've found this useful:
namespace Services {
class LogService {
info(message: string, ...optionalParams: any[]) {
if (optionalParams && optionalParams.length > 0) {
console.log(message, optionalParams);
return;
}
console.log(message);
}
}
}
class ExampleSystemUnderTest {
constructor(private log: Services.LogService) {
}
doIt() {
this.log.info('done');
}
}
// I export this in a common test file
// with other utils that all tests import
const asSpy = f => <jasmine.Spy>f;
describe('SomeTest', () => {
let log: Services.LogService;
let sut: ExampleSystemUnderTest;
// ARRANGE
beforeEach(() => {
log = jasmine.createSpyObj('log', ['info', 'error']);
sut = new ExampleSystemUnderTest(log);
});
it('should do', () => {
// ACT
sut.doIt();
// ASSERT
expect(asSpy(log.error)).not.toHaveBeenCalled();
expect(asSpy(log.info)).toHaveBeenCalledTimes(1);
expect(asSpy(log.info).calls.allArgs()).toEqual([
['done']
]);
});
});
Angular 9
Using jasmine.createSpyObj is ideal when testing a component where a simple service is injected. For example: let's say, in my HomeComponent I have a HomeService (injected). The only method in the HomeService is getAddress().
When creating the HomeComponent test suite, I can initialize the component and service as:
describe('Home Component', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
let element: DebugElement;
let homeServiceSpy: any;
let homeService: any;
beforeEach(async(() => {
homeServiceSpy = jasmine.createSpyObj('HomeService', ['getAddress']);
TestBed.configureTestingModule({
declarations: [HomeComponent],
providers: [{ provide: HomeService, useValue: homeServiceSpy }]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
element = fixture.debugElement;
homeService = TestBed.get(HomeService);
fixture.detectChanges();
});
}));
it('should be created', () => {
expect(component).toBeTruthy();
});
it("should display home address", () => {
homeService.getAddress.and.returnValue(of('1221 Hub Street'));
fixture.detectChanges();
const address = element.queryAll(By.css(".address"));
expect(address[0].nativeNode.innerText).toEqual('1221 Hub Street');
});
});
This is a simple way to test your component using jasmine.createSpyObj. However, if your service has more methods more complex logic, I would recommend creating a mockService instead of createSpyObj. For example:
providers: [{ provide: HomeService, useValue: MockHomeService }]
Hope this helps!
Building on #Eric Swanson's answer, I've created a better readable and documented function for using in my tests. I also added some type safety by typing the parameter as a function.
I would recommend to place this code somewhere in a common test class, so that you can import it in every test file that needs it.
/**
* Transforms the given method into a jasmine spy so that jasmine functions
* can be called on this method without Typescript throwing an error
*
* #example
* `asSpy(translator.getDefaultLang).and.returnValue(null);`
* is equal to
* `(translator.getDefaultLang as jasmine.Spy).and.returnValue(null);`
*
* This function will be mostly used in combination with `jasmine.createSpyObj`, when you want
* to add custom behavior to a by jasmine created method
* #example
* `const translator: TranslateService = jasmine.createSpyObj('TranslateService', ['getDefaultLang'])
* asSpy(translator.getDefaultLang).and.returnValue(null);`
*
* #param {() => any} method - The method that should be types as a jasmine Spy
* #returns {jasmine.Spy} - The newly typed method
*/
export function asSpy(method: () => any): jasmine.Spy {
return method as jasmine.Spy;
}
Usage would be as follows:
import {asSpy} from "location/to/the/method";
const translator: TranslateService = jasmine.createSpyObj('TranslateService', ['getDefaultLang']);
asSpy(translator.getDefaultLang).and.returnValue(null);

Categories