Angular 2 unit testing "Cannot read property 'unsubscribe' of undefined" - javascript

I have sample TestComp with ngOnInit and ngOnDestroy methods and ActivatedRoute subscription.
#Component({
selector: 'test',
template: '',
})
export class TestComp implements OnInit, OnDestroy {
constructor (
private route: ActivatedRoute
) {
}
ngOnInit() {
this.subscription = this.route.data
.subscribe(data => {
console.log('data', data.name);
})
;
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
I am getting "Cannot read property 'unsubscribe' of undefined" when I call ngOnDestroy method from spec file (or when I am running multiple tests).
My spec file:
describe('TestComp', () => {
let comp: TestComp;
let fixture: ComponentFixture<TestComp>;
beforeEach(async(() => {
TestBed
.configureTestingModule({
declarations: [TestComp],
imports: [RouterTestingModule],
providers: [
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
name: 'Stepan'
})
}
}
}
// { //Also tried this
// provide: ActivatedRoute,
// useValue: {
// params: Observable.of({name: 'Stepan'})
// }
// }
]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(TestComp);
fixture.detectChanges();
})
}));
it('TestComp successfully initialized', () => {
fixture.componentInstance.ngOnInit();
expect(fixture.componentInstance).toBeDefined()
fixture.componentInstance.ngOnDestroy();
});
});
I am passing ActivatedRoute value based on answers here, but I am getting error. So my question is - what should I pass as ActivatedRoute to make it possible to subscribe and unsubscribe?
Example Plunker.

Just use observer when mock your service:
{
provide: ActivatedRoute,
useValue: { data: Observable.of({ name: 'Stepan' }) }
}

You should import Subscription first.
import { Subscription } from 'rxjs/Subscription';

Related

Angular 10 Test: Component Resolver Data Subscription Error

I've spend the last few days trying to get up to speed with ng test and all the spec files #angular/cli creates when creating components and, well, pretty much else.
As I was working on my own portfolio website, I have come across an issue that I cannot seem to understand or fix.
I have this component (pretty vanilla stuff):
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Title } from '#angular/platform-browser'
import { ProjectDetails } from './project-details'
#Component({
selector: 'app-projects-details',
templateUrl: './projects-details.component.html',
styleUrls: ['./projects-details.component.sass']
})
export class ProjectsDetailsComponent implements OnInit {
// Class variables
currentContent: ProjectDetails
constructor(
private route : ActivatedRoute,
private title: Title
) { }
ngOnInit() {
// Assign the data to local variable for use
this.route.data.subscribe(content => {
this.currentContent = content.project.view //<-- This line causes the issue
// Set the title for the Projects view
this.title.setTitle(this.currentContent.view_title)
})
}
}
And this spec file (more vanilla stuff):
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing'
import { ProjectsDetailsComponent } from './projects-details.component';
import { ProjectDetails } from './project-details'
describe('ProjectsDetailsComponent', () => {
let component: ProjectsDetailsComponent;
let fixture: ComponentFixture<ProjectsDetailsComponent>;
const projectDetails : ProjectDetails = { /* valid object content */ }
beforeEach(async(() => {
TestBed.configureTestingModule({
imports:[
RouterTestingModule
],
declarations: [ ProjectsDetailsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProjectsDetailsComponent);
component = fixture.componentInstance;
component.currentContent = projectDetails
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
When running the tests, I get this error:
TypeError: content.project is undefined in http://localhost:9876/_karma_webpack_/main.js (line 1576)
So, I'm not sure exactly what's going on here. No matter what I do, the error prevails.I have a similarly setup component that doesn't have this issue and a side by side comparison shows no differences in the spec.ts file aside from imports.
I tried changing the file to this:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing'
import { ProjectsDetailsComponent } from './projects-details.component';
import { ProjectDetails } from './project-details'
import { ActivatedRoute } from '#angular/router';
describe('ProjectsDetailsComponent', () => {
let component: ProjectsDetailsComponent;
let fixture: ComponentFixture<ProjectsDetailsComponent>;
const projectDetails : ProjectDetails = {/* valid content */}
beforeEach(async(() => {
TestBed.configureTestingModule({
// imports:[
// RouterTestingModule
// ],
providers: [
{ provide: ActivatedRoute, useValue: projectDetails }
],
declarations: [ ProjectsDetailsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProjectsDetailsComponent);
component = fixture.componentInstance;
component.currentContent = projectDetails
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Which changes the error to this (which confuses me more):
TypeError: this.route.data is undefined in http://localhost:9876/_karma_webpack_/main.js (line 1575)
The question to the community: how do I fix this? What's the reason this error is coming up?
Instead of providing the raw projectDetails, provide an Observable in its data property:
import {of} from 'rxjs';
...
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
// Properly provide the activated route mock object.
{ provide: ActivatedRoute, useValue: { data: of(projectDetails) } }
],
declarations: [ ProjectsDetailsComponent ]
})
.compileComponents();
}));
...
If you look at how you access the route data, you can see that it uses an Observable:
this.route.data.subscribe(content => {...});

How to test data-cy directive?

I don't know how to write unit-test for this directive. Can you help me?
import { Directive, ElementRef, Inject, Input, Renderer2 } from enter code here'#angular/core';
#Directive({
// tslint:disable-next-line:directive-selector
selector: '[data-cy]'
})
export class DataCyDirective {
#Input('data-cy') dataCy: string;
constructor(
private el: ElementRef,
private renderer: Renderer2,
#Inject('production') isProd: boolean
) {
if (isProd) {
renderer.removeAttribute(el.nativeElement, 'data-cy');
}
}
}
I'm found a solution
#Component({
template: `
<span id="expected" data-cy="elementCy"></span>
`
})
class DataCyDirectiveTestComponent implements OnInit {
constructor(#Inject('production') prod: boolean) {}
}
describe('DataCyDirective test version', () => {
let component: DataCyDirectiveTestComponent;
let fixture: ComponentFixture<DataCyDirectiveTestComponent>;
configureTestSuite();
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [DataCyDirectiveTestComponent, DataCyDirective],
providers: [{ provide: 'production', useValue: false }]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DataCyDirectiveTestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should NOT delete the data-cy attribute if production is not enabled', () => {
const el: ElementRef<HTMLElement> = fixture.debugElement.query(By.css('#expected'));
expect(el.nativeElement.getAttribute('data-cy')).not.toBeNull();
});
});

How to test subscribe function in angular onInit method?

I have the following component that I want to test:
component:
constructor(
private countrystore: Store<CountryAppState>,
private docstore: Store<DocumentAppState>,
private formBuilder: FormBuilder,
) {}
ngOnInit() {
this.getCountryState = this.countrystore.select('selecttimaticCountryState');
this.getCountryState.subscribe((state) => {
this.countries = state.response;
});
Spec file:
describe('TravellerInfoComponent', () => {
let component: TravellerInfoComponent;
let fixture: ComponentFixture<TravellerInfoComponent>;
Object.defineProperty(window, "matchMedia", {
value: jest.fn(() => { return { matches: true } })
});
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({}),
EffectsModule.forRoot([]),
BrowserAnimationsModule,
HttpClientModule
],
providers: [
FormsModule
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TravellerInfoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
Before I can even write a test I'm getting the following error:
I have looked at similar answers that suggest using the 'of' rxjs operator to simulate an observable, others suggest using a spyOn technique. But I don't quite understand where this should be inserted. Any help for a testing noob would be great.
const countrystore = TestBed.get(Store<CountryAppState>);
spyOn(countrystore, 'select').and.callFake(() => {
return of(someTestDataYouCreate);
});

How to test that I have loaded my JSON - Angular2+

I have an Angular method that simply loads the contents of a locally stored JSON file featuring an array, however I cannot seem to test it.
test.ts (shortened for conciseness)
describe('MyComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [HttpClientTestingModule],
});
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
component.ngOnInit();
it('should load status data from local json', () => {
const data = require('../../../assets/myData.json');
component.getThings();
expect(component.data).toEqual(data);
});
}
MyComponent.ts
data: string[];
constructor(private httpClient: HttpClient) {}
ngOnInit() {
this.getThings().subscribe(data =
this.data = data;
}
}
getData(): Observable<any> {
const data = '../../../assets/data.json';
return this.httpClient.get(data);
}
When testing for http request you need to mock the request.
Check out more about HttpClientTestingModule below:
https://angular.io/api/common/http/testing/HttpClientTestingModule
https://medium.com/spektrakel-blog/angular-testing-snippets-httpclient-d1dc2f035eb8
The below code is working,updated the component code as well a bit:
Component:
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
#Component({
selector: 'app-load-local-file',
templateUrl: './load-local-file.component.html',
styleUrls: ['./load-local-file.component.css']
})
export class LoadLocalFileComponent implements OnInit {
data: string[];
constructor(private httpClient: HttpClient) { }
ngOnInit() {
this.getData().subscribe(data => {
this.data = data;
});
}
getData(): Observable<any> {
const data = './data.json';
return this.httpClient.get(data);
}
}
Spec:
import { TestBed, async, fakeAsync, tick } from '#angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '#angular/common/http/testing'
import { LoadLocalFileComponent } from 'src/app/load-local-file/load-local-file.component';
describe('MyComponent', () => {
let fixture, component, httpMock: HttpTestingController;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [LoadLocalFileComponent],
imports: [HttpClientTestingModule]
});
fixture = TestBed.createComponent(LoadLocalFileComponent);
component = fixture.componentInstance;
httpMock = TestBed.get(HttpTestingController);
}));
it('should load status data from local json', fakeAsync(() => {
const data = require('./data.json');
component.ngOnInit();
tick();
const request = httpMock.expectOne('./data.json');
request.flush(data);
expect(component.data).toEqual(data);
}));
});

Unit test queryParams subscription (Angular5)

i'm having problems testing the logic inside ActivatedRoute queryParams subscription.
constructor(private router: Router, private route: ActivatedRoute, private auth: AuthService) {}
ngOnInit(): void {
this.route.queryParams.subscribe((params:any) => {
if(params['data']) {
this.handle(params['data']);
} else {
if (this.auth.isAuthenticated()) {
this.router.navigate(['/home']);
}
}
});
}
I would like to test:
If this.handle() is triggered when mocked params['data'] is supplied
If there is no params and this.auth.isAuthenticated() returns true that this.router.navigate is called
I have tried multiple things and i'm running out of ideas.
My test file:
describe('TestComponent', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
const mockService = {
navigate: jasmine.createSpy('navigate'),
isAuthenticated: jasmine.createSpy('isAuthenticated'),
queryParams: jasmine.createSpy('queryParams')
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TestComponent],
providers: [
{ provide: Router, useValue: mockService },
{ provide: ActivatedRoute, useValue: mockService },
{ provide: AuthService, useValue: mockService }
]
}).compileComponents();
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
mockService.navigate.calls.reset();
}));
it('should create the test component', () => {
expect(component).toBeTruthy();
});
it('should navigate away when authenticated', () => {
mockService.isAuthenticated.and.returnValue(true);
mockService.queryParams.and.callFake((data, params) => new Observable(o => o.next({ params: {} })));
component.ngOnInit();
expect(mockService.navigate).toHaveBeenCalledWith(['/home']);
});
});
But with that i get TypeError: this.route.queryParams.subscribe is not a function. I know that mockService.isAuthenticated.and.returnValue(true); is working correctly because before using subscription to params i had only this if statement inside ngOnInit().
I have tried to change the mockService to:
const mockService = {
navigate: jasmine.createSpy('navigate'),
isAuthenticated: jasmine.createSpy('isAuthenticated'),
queryParams: {
subscribe: jasmine.create('subscribe')
}
};
I also tried with:
const mockService = {
navigate: jasmine.createSpy('navigate'),
isAuthenticated: jasmine.createSpy('isAuthenticated'),
queryParams: {
subscribe: Observable.of({ params: {} })
}
};
But no success, for those last two i get Expected spy navigate to have been called with [ [ '/home' ] ] but it was never called.
So does someone know how to correctly test logic inside querParams subscription?
You can use rxjs observable in useValue to mock it.
const router = {
navigate: jasmine.createSpy('navigate')
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([]),
RouterTestingModule,
PlanPrimengModule
],
declarations: [YourComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
provideMockStore(),
{
provide: ActivatedRoute,
useValue: {
queryParams: of({
param1: "value1",
param1: "value2"
})
}
},
{ provide: Router, useValue: router }
]
}).compileComponents(); }));
I can't say if it is too late to answer this or not, but maybe it will help new googlers...
I manage to solve that this way:
class ActivatedRouteMock {
queryParams = new Observable(observer => {
const urlParams = {
param1: 'some',
param2: 'params'
}
observer.next(urlParams);
observer.complete();
});
}
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [
HttpClient,
HttpHandler,
ControlContainer,
{
provide: ActivatedRoute,
useClass: ActivatedRouteMock
}
]
}).compileComponents();
injector = getTestBed();
httpMock = injector.get(HttpTestingController);
}));
That allows you to assert the logic inside your .subscribe(() => {}) method..
Hope it hepls

Categories

Resources