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

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*/);

Related

Angular Test Jest - TypeError: Cannot read property 'queryParams' of undefined

I've seen the same error in other posts but they didn't work for me.
I have an Angular component, where I need to read a queryParam from the url, for example in http://localhost:4200/sample-page?page=3 I want to stores the number 3 into a component local variable.
/**
* Set page by url parameter
*/
export const setPaginationMarkByUrlParam = (activatedRoute): number => {
// Get page param from Url to set pagination page
const pageParam = activatedRoute.snapshot.queryParams;
return pageParam.page ? Number(pageParam.page) : 1;
};
This function is in another file and I put as parameter the activeRoute, which comes from the ngOnInit of the component in which I want to get the queryParam.
ngOnInit() {
this.page = setPaginationMarkByUrlParam(this.activatedRoute);
}
This code works perfectly, but when the Jenkins pipeline runs the npx jest tests, I get the following message:
TypeError: Cannot read property 'queryParams' of undefined
My Spec.ts:
beforeEach(() => {
TestBed.configureTestingModule({
...,
providers: [
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) =>
fn({
pagingParams: {
predicate: 'id',
reverse: false,
page: 0
}
})
}
}
}
]...
it('Should call load all on init', () => {
// GIVEN
const headers = new HttpHeaders().append('link', 'link;link');
spyOn(service, 'query').and.returnValue(
of(
new HttpResponse({
body: [new DataSource(123)],
headers
})
)
);
// WHEN
comp.ngOnInit();
// THEN
expect(service.query).toHaveBeenCalled();
expect(comp.dataSources[0]).toEqual(jasmine.objectContaining({ id: 123 }));
});
The test fails in comp.ngOnInit(); function.
I don't have any kind of private variables, the activeRoute that comes as a parameter, I tried it with public and private.
Looking at both StackOverflow and GitHub Issues I have not been able to fix this problem.
Thank you very much!
While you are mocking data, you are not mocking snapshot on ActivatedRoute. You have three choices to accomplish this:
First, you should consider using an ActivatedRouteStub as described in the docs. This then makes is as easy as: activatedRoute.setParamMap({page: 3}); to set any queryParameter you want to set. This option requires more test code
Next option: this would mock the queryParameter of page on the ActivatedRoute with an Observable:
provide: ActivatedRoute, useValue: {
snapshot: of(queryParams: { page: 3 }),
}
If, for a reason not disclosed in your question, you do not need an Observable from your ActivatedRoute, this would be the alternate code:
provide: ActivatedRoute, useValue: {
snapshot: { queryParams: { page: 3 } }
}
Finally, the code you provided doesn't have a call to inject for the ActivatedRoute provider nor does it show the test component creation. So at a minimum ensure you are doing that as well:
Either:
fixture = TestBed.createComponent(...);
Or:
activatedRoute = TestBed.inject(ActivatedRoute);
If none of these suggestions solve your problem, put a a minimal StackBlitz that demonstrates the problem and we'll get it working.
Generally for configuring/mocking different RouteOptions when you have ActivatedRoute with Jest you should use:
createRoutingFactory from #ngneat/spectator/jest
and pass one or more of the RouteOptions properties like (params, queryParams, parent etc.) directly on its constructor
For example:
const createComponent = createRoutingFactory({
component: Component,
imports: [RouterTestingModule],
parent: {
snapshot: {
queryParamMap: convertToParamMap({
//...
}),
paramMap: convertToParamMap({
//...
})
}
}
});

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

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

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.

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