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);
});
Related
I have created components and pages in my ionic 5 project.
The pages are working fine in unit tests.
But, the components are not being tested in the same way.
describe('component', () => {
let component: Component;
let fixture: ComponentFixture<Component>;
let modalSpy = jasmine.createSpyObj('Modal', ['present', 'onDidDismiss', 'catch', 'dismiss']);
modalSpy.onDidDismiss.and.returnValue(Promise.resolve(true));
let modalCtrlSpy = jasmine.createSpyObj('ModalController', ['create', 'dismiss']);
modalCtrlSpy.create.and.callFake(function () {
return modalSpy;
});
let popoverSpy = jasmine.createSpyObj('Popover', ['present', 'onDidDismiss', 'dismiss']);
popoverSpy.onDidDismiss.and.returnValue(Promise.resolve(true));
let popoverCtrlSpy = jasmine.createSpyObj('PopoverController', ['create', 'dismiss']);
popoverCtrlSpy.create.and.callFake(function () {
return popoverSpy;
});
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ Component ],
imports: [
IonicModule.forRoot(),
TranslateModule.forChild(),
ComponentsModule,
PipesModule,
TranslateModule.forRoot({
loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }
})
],
providers: [
{ provide: ModalController, useValue: modalCtrlSpy },
{ provide: PopoverController, useValue: popoverCtrlSpy },
AppGlobalState,
IonContent,
HttpClient,
HttpHandler,
Http,
HttpClientTestingModule,
Api,
ConnectionBackend,
SQLiteService,
SQLite,
InAppBrowser,
RequestOptions
]
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(Component);
component = fixture.componentInstance;
fixture.detectChanges();
component.loading=false;
component.ngOnInit();
fixture.detectChanges();
}))
afterEach(async () => {
component.ngOnDestroy();
})
This does not show the component being loaded.
<ion-card *ngIf='loading'>
</ion-card>
<ion-card *ngIf='!loading'>
</ion-card>
public loading: boolean = true
ngOnInit() {
subscriber.subscribe(() => {
this.loading = false
})
}
another issue in the same code is when I added renderer2. It gives error can not read property style of undefined. for the function setStyle being used in code.
any help is appreciated.
I am deperately trying to test a component that is using a service. I use spy to mock it but every time I run tests it fails with exception:
Cannot read property 'subscribe' of undefined
My test looks like this:
describe('FilmOverviewComponent', () => {
let component: FilmOverviewComponent;
let fixture: ComponentFixture<FilmOverviewComponent>;
let filmsServiceSpy: jasmine.SpyObj<FilmsService>;
beforeEach(async(() => {
const spy = jasmine.createSpyObj('FilmsService',
['searchFilmByID']
);
TestBed.configureTestingModule({
declarations: [ FilmOverviewComponent ],
providers: [
{provide: AppTitleService, useValue: {getTitle: () => 'title'}},
{provide: ActivatedRoute, useValue: {params: of({id: 123})} },
{provide: FilmsService, useValue: spy}
]
})
.compileComponents();
filmsServiceSpy = TestBed.get(FilmsService);
}));
beforeEach(() => {
filmsServiceSpy.searchFilmByID.and.returnValue(Observable.create([{title: "", year: ""}]));
fixture = TestBed.createComponent(FilmOverviewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Methods used in the service:
searchFilmByID(movieID: string): Observable<Film> {
return this.http.get<Film>(this.getUrlWithID(movieID));
}
private getUrlWithID(movieID: string) {
return 'api/externalfilms/film/' + movieID;
}
I have no idea how to tackle this. I suspect that it would be resolved with some kind of mocking of subscribe method but I completely failed at that.
Thank you for your help in advance!
The error is from this.http.get<Film>....
There are 2 ways to solve it.
First way - mock http client service and call fake
describe('FilmOverviewComponent', () => {
let component: FilmOverviewComponent;
let fixture: ComponentFixture<FilmOverviewComponent>;
let filmsService: FilmsService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FilmOverviewComponent ],
imports: [ HttpClientTestingModule ],
providers: [
{provide: AppTitleService, useValue: {getTitle: () => 'title'}},
{provide: ActivatedRoute, useValue: {params: of({id: 123})} },
FilmsService
]
})
.compileComponents();
}));
beforeEach(() => {
filmsService = TestBed.inject(FilmsService);
/* Mock response */
const httpClient: HttpClient = TestBed.inject(HttpClient);
spyOn(httpClient, 'get').and.callFake(() => of({title: "", year: ""}));
fixture = TestBed.createComponent(FilmOverviewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Second way - call fake service method
const FilmsServiceStub = jasmine.createSpyObj('FilmsService', ['searchFilmByID']);
describe('FilmOverviewComponent', () => {
let component: FilmOverviewComponent;
let fixture: ComponentFixture<FilmOverviewComponent>;
let filmsService: FilmsService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FilmOverviewComponent ],
providers: [
{provide: AppTitleService, useValue: {getTitle: () => 'title'}},
{provide: ActivatedRoute, useValue: {params: of({id: 123})} },
{provide: FilmsService, useValue: FilmsServiceStub}
]
})
.compileComponents();
}));
beforeEach(() => {
filmsService = TestBed.inject(FilmsService);
/* Mock response */
spyOn(filmsService, 'searchFilmByID').and.callFake(() => of({title: "", year: ""}));
fixture = TestBed.createComponent(FilmOverviewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
I'm trying to start testing my component. The first thing that I wanted to test is if the ngOnInit calls the correct services.
agreement.component.ts:
constructor(private agreementService: AgreementService,
private operatorService: OperatorService,
private accountService: AccountService,
private route: ActivatedRoute,
private router: Router,
private sessionService: SessionService,
private settingsService: SettingsService) {
this.agreementId = Number(this.route.snapshot.paramMap.get('agreementId'));
}
async ngOnInit() {
this.session = await this.sessionService.getSession();
this.settings = await this.settingsService.getSettings();
this.operatorService.getOperators(this.session.bic).subscribe(data => {
this.operators = data;
});
...
}
agreement.component.spec.ts
import {AgreementComponent} from './agreement.component';
import {async, TestBed} from '#angular/core/testing';
import {ActivatedRoute, convertToParamMap, Router} from '#angular/router';
import {RouterTestingModule} from '#angular/router/testing';
import {AgreementService} from '../../../services/agreement.service';
import {AccountService} from '../../../services/account.service';
import {SessionService} from '../../../services/session.service';
import {SettingsService} from '../../../services/settings.service';
describe('agreementComponent', () => {
let mockAgreementService: AgreementService;
let mockOperatorService;
let mockAccountService: AccountService;
let mockRoute: ActivatedRoute;
let mockRouter: Router;
let mockSessionService: SessionService;
let mockSettingsService: SettingsService;
let component: AgreementComponent;
beforeEach(async(() => {
mockAgreementService = jasmine.createSpyObj(['getAgreement']);
mockOperatorService = jasmine.createSpyObj(['getOperators']);
mockAccountService = jasmine.createSpyObj(['getFeeAccounts']);
mockRoute = jasmine.createSpyObj(['route']);
mockRouter = jasmine.createSpyObj(['router']);
mockSessionService = jasmine.createSpyObj(['getSession']);
mockSettingsService = jasmine.createSpyObj(['getSettings']);
TestBed.configureTestingModule({
declarations: [AgreementComponent],
imports: [
RouterTestingModule
],
providers: [
{
provide: ActivatedRoute, useValue:
{
snapshot: {
paramMap: convertToParamMap({agreementId: '0'})
}
}
},
]
});
component = new AgreementComponent(mockAgreementService, mockOperatorService, mockAccountService,
mockRoute, mockRouter, mockSessionService, mockSettingsService);
}));
it('should call operators service', () => {
component.ngOnInit();
expect(mockOperatorService).toHaveBeenCalled();
});
});
Currently I'm getting:
Failed: Cannot read property 'paramMap' of undefined
TypeError: Cannot read property 'ngOnInit' of undefined
I'm really sure this code lacks a lot of things in order to work fine, I just can't figure out what exactly is missing and what should be done differently, because googling my errors got me confused with ton of different solutions. I'm pretty new with angular testing so would like to have some pieces of advice how to write tests in the correct way.
Take a different approach by creating Stubs as explained in one of my articles.
Created reusable stubs as:
export class MockOperatorService{
getOperators(){
return of({data: "someVal"})
}
}
and so on for other other services.
Use RouterTestingModule as and when required in imports
Mock ActivatedRoute and other services in as below:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AgreementComponent],
imports: [
RouterTestingModule
],
providers: [
{
provide: ActivatedRoute, useValue:
{
snapshot: {
paramMap: convertToParamMap({agreementId: '0'})
}
}
},
{provide: OperatorService , useClass: MockOperatorService},
{....similarly for AgreementService etc etc}
]
});
}));
beforeEach(() => {
fixture = TestBed.createComponent(AgreementComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
I realized that there is a lack of proper set of articles to learn about angular testing so I wrote collection of articles which you can find on the bottom of this page. I hope it'll help
Update:
To spy as asked in comment, you can do:
it('should call getOperators service in ngOnInit', () => {
spyOn(component.operatorService,"getOperators").and.callThrough();
component.ngOnInit();
expect(component.operatorService.getOperators).toHaveBeenCalled();
// you can also be more specific by using ".toHaveBeenCalledWith()"
});
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
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';