I have implemented the component depicted below which uses an image, material icons, and a custom ticker directive that scrolls either line of text if it is too long for the element.
Now I'm trying to learn unit testing using karma (via angular cli/webpack) and I have the majority of the configuration figured out to create the component, but I'm struggling to understand how to configure for images, material icons, and get the directive HostListener to work.
Here is what I have created so far:
/* config */
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement, NO_ERRORS_SCHEMA } from '#angular/core';
import { TickerDirective } from '../../directives/ticker.directive';
import { MdIconModule, MaterialModule } from '#angular/material';
import { MdIconRegistry } from '#angular/material/icon';
/* my stuff */
import { FoodListComponent } from './food-list.component';
import { FoodDataService } from '../../services/food-items/food-data.service';
import { FoodItem } from '../../diet/food-item';
import { WorkingData } from '../../services/working-data/working-data';
import { WorkingDataService } from '../../services/working-data/working-data.service';
describe('FoodListComponent', () => {
let component: FoodListComponent;
let fixture: ComponentFixture<FoodListComponent>;
let foodDataService: FoodItem[];
let workingDataService: WorkingData;
let de: DebugElement[];
let el: HTMLElement;
/* Stub Services */
let foodDataServiceStub = [{
name: 'test food name ..................', // written long to trigger the ticker directive
img: './no_image.png',
description: 'test food description'
}];
let workingDataServiceStub = {
today: new Date(),
selectedDate: new Date(2016, 2, 5),
targetDate: new Date(2016, 2, 7),
data: {exercise: 'Squat'}
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FoodListComponent, TickerDirective ],
imports: [ MaterialModule.forRoot(), MdIconModule], // not sure if this is correct
providers: [
{ provide: FoodDataService, useValue: foodDataServiceStub },
{ provide: WorkingDataService, useValue: workingDataServiceStub } ,
MdIconRegistry // not sure if this is correct
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FoodListComponent);
component = fixture.componentInstance;
/* Inject services */
foodDataService = TestBed.get(FoodDataService);
workingDataService = TestBed.get(WorkingDataService);
/* Assign Services */
component.workingData = workingDataService;
component.foods = foodDataService;
fixture.detectChanges();
de = fixture.debugElement.queryAll(By.css('span'));
el = de[0].nativeElement;
// console.log(el);
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have the correct food name', () => {
expect(el.textContent).toContain('test food name ..................');
});
});
Image
I have a png 'no_image.png' in the same folder as the spec.ts file. It seems to locate the image because there is no 404 error (as it did when I put in a wrong path), but the image is not rendered.
Ticker Directive
The ticker renders the correct span so it seems to set up the ticker properly, but the the HostListener doesn't seem to pick up on the mouseover event to trigger the directve action. I tried importing HostListener into TestBed but that through an error.
Material Icons
You can see the ligatures of the material icons, but they are not rendering. I read that I needed to import Http but that through an error.
Help in implementing any or all of these would be greatly appreciated, but I would also like to hear how I could go about problem solving these kinds of issues in the future (my google searches did not yield helpful results).
you don't have so high-level logic in this component and you can not writ so big unit test, for that component actually integration test is usable, so you can test the value, color, size of your component.
INTEGRATION TESTING is a level of software testing where individual units are combined and tested as a group. The purpose of
this level of testing is to expose faults in the interaction between
integrated units. Test drivers and test stubs are used to assist in
Integration Testing.
read more
Related
I am currently working on an Ionic (v3) app and we have several tests to test services, pages and components.
Everything was working fine until I added a new test for a component.
Both tests run fine individually (if started with fdescribe, or if I comment one of them out).
The tests look like this:
verify-key.spec.ts
describe('Component: VerifyKey', () => {
let component: VerifyKeyComponent
let fixture: ComponentFixture<VerifyKeyComponent>
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [VerifyKeyComponent],
imports: [
IonicModule.forRoot(VerifyKeyComponent)
]
})
// create component and test fixture
fixture = TestBed.createComponent(VerifyKeyComponent)
// get test component from the fixture
component = fixture.componentInstance
})
...
})
wallet-select-coins.spec.ts
describe('Wallet-Select-Coin Component', () => {
let fixture: ComponentFixture<WalletSelectCoinsPage>
let component: WalletSelectCoinsPage
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [WalletSelectCoinsPage],
imports: [
IonicModule.forRoot(WalletSelectCoinsPage),
ComponentsModule,
IonicStorageModule.forRoot({
name: '__airgap_storage',
driverOrder: ['localstorage']
})
],
providers: [
SecretsProvider,
{
provide: SecureStorageService,
useFactory: SecureStorageFactory,
deps: [Platform]
},
{ provide: NavController, useClass: NavControllerMock },
{ provide: NavParams, useClass: NavParamsMock },
{ provide: StatusBar, useClass: StatusBarMock },
{ provide: SplashScreen, useClass: SplashScreenMock },
{ provide: Platform, useClass: PlatformMock }
]
})
}))
beforeEach(() => {
fixture = TestBed.createComponent(WalletSelectCoinsPage)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should not show hd-wallet dropdown if currency does not support it', () => {
let el = fixture.debugElement.nativeElement
let ethereumRadio = el.querySelector('#eth')
// click on ethereum
ethereumRadio.click()
fixture.detectChanges()
console.log(component.selectedProtocol)
expect(component.selectedProtocol).toBeDefined() // This fails
expect(component.selectedProtocol.identifier).toEqual('eth')
// eth should not show hd wallets
let hdWalletSelector = el.querySelector('#wallet-type-selector')
expect(hdWalletSelector).toBeFalsy()
})
})
If both tests are enabled, the second one fails at the line expect(component.selectedProtocol).toBeDefined() with the error Expected undefined to be defined.
If I comment out the line fixture = TestBed.createComponent(VerifyKeyComponent) from the first file, then the second test runs without any issues.
My first idea was that the TestBed somehow gets modified in the first test. So I tried adding TestBed.resetTestingModule() after the first test, but that didn't change anything.
Any help would be greatly appreciated.
When writing tests its best practice to clean up everything after every test so that all test can run in random order without effecting any other test.
I would suggest to introduce a afterEach() or afterAll() in every test which clears any data which is produced. Because most test environments run all tests in the same context.
And why would you like that your TestBed.configureTestingModule to be created async()? Your test will run but the async part will probably never get called before the it(..
Hope these ideas will help :)
For some time I am trying to rewrite my 'broken' unit tests since lazy loading with IonicPageModule, without any luck.
Anyone who has some pointers or working examples is greatly appreciated.
Should no working example exist (which I doubt), I will help create one with the feedback gathered on this page.
Any help is welcome.
Thanks to the help of RomainFallet I've found (and recreated) a working ionic 3 Unit Test with lazy loading on his github (see 2.7), which I also share here for ease.
/* Import Ionic & Angular core elements */
import { async, TestBed } from '#angular/core/testing';
import { IonicModule } from 'ionic-angular';
/* Import modules */
import { HomePageModule } from './home.module';
/* Import components */
import { AppComponent } from '../../app/app.component';
/* Import pages */
import { HomePage } from './home';
describe('HomePage', () => {
let fixture;
let component;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [],
imports: [
IonicModule.forRoot(AppComponent),
HomePageModule
],
providers: [
]
})
}));
beforeEach(() => {
fixture = TestBed.createComponent(HomePage);
component = fixture.componentInstance;
});
it ('should create a valid instance of HomePage', () => {
expect(component instanceof HomePage).toBe(true);
});
});
Hope it also helps others finding their way.
I am writing some tests for my Angular2 application according to the documentation but I have run into a problem which I can't seem to fix. I get the following error when trying to launch the spec runner:
Failed: This test module uses the component CategoriesComponent which is using a "templateUrl", but they were never compiled. Please call "TestBed.compileComponents" before your test.
I understand this is happening as I am using a seperate template file for the template within the component but I jhave tried multilpe solutions which don't seem to work.
Here is my component under test:
import { Component } from "#angular/core";
#Component({
selector: 'categories-component',
templateUrl: '/app/views/catalog/categories/categories-dashboard.html',
moduleId: module.id
})
export class CategoriesComponent {
title: 'Categories;
}
The categories-dashboard.html file:
<h1>{{ title }}</h1>
and my testing module for the component:
import {TestBed, ComponentFixture, ComponentFixtureAutoDetect, async} from "#angular/core/testing";
import { By} from "#angular/platform-browser";
import { CategoriesComponent } from "../../../../components/catalog/categories/CategoriesComponent";
import { DebugElement } from "#angular/core";
let comp: CategoriesComponent;
let fixture: ComponentFixture<CategoriesComponent>;
let de: DebugElement;
let el: HTMLElement;
describe('BannerComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CategoriesComponent ],
providers: [
{ provide: ComponentFixtureAutoDetect, useValue: true }
]
});
TestBed.compileComponents();
fixture = TestBed.createComponent(CategoriesComponent);
comp = fixture.componentInstance; // BannerComponent test instance
// query for the title <h1> by CSS element selector
de = fixture.debugElement.query(By.css('h1'));
el = de.nativeElement;
}));
it('should display original title', () => {
expect(el.textContent).toContain(comp.title);
});
});
I have tried to implement TestBed.compileComponents() into into the component but wherever I put it it doesn't seem to work.
Can anyone see why this error is occurring or point me in the directoin of a solution?
Thanks!
compileComponents resolves asynchronously (as it makes an XHR for the template), so it returns a promise. You should handle anything requiring the resolution of the promise, inside of the then callback of the promise
TestBed.compileComponents().then(() =>{
fixture = TestBed.createComponent(CategoriesComponent);
comp = fixture.componentInstance; // BannerComponent test instance
// query for the title <h1> by CSS element selector
de = fixture.debugElement.query(By.css('h1'));
el = de.nativeElement;
});
I'm just now learning unit testing in general, but I'm simultaneously learning angular2. Angular CLI created my test scaffolds and I've configured the testing module with the neccessary component and directive. I'm trying to provide a stub service similar to the one decribed in the DOCS, but I'm getting the following error:
Error: Cannot create the component FoodListComponent as it was not imported
into the testing module
This is odd because the should create test passed when with the FoodListComponent before I added the service into the providers: []. My service works fine in the application, but I can provide it too if you need to see it.
Basically I'd like to know where in this testing set up I need to put the foodDataServiceStub, whether I'm adding the stub service to the TestBed object correctly, and where I need to inject the service via foodDataService = TestBed.get(FoodDataService);?
I'd be thankful for an explanation as well.
food-list.component.spec.ts
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement, NO_ERRORS_SCHEMA } from '#angular/core';
import { TickerDirective } from '../../directives/ticker.directive';
import { FoodListComponent } from './food-list.component';
describe('FoodListComponent', () => {
let component: FoodListComponent;
let fixture: ComponentFixture<FoodListComponent>;
let foodDataServiceStub = {
name: 'test food name',
img: '',
description: 'test food description'
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FoodListComponent, TickerDirective ],
providers: [ { provide: FoodDataService, useValue: foodDataServiceStub } ], /* This line gives me the error */
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FoodListComponent);
component = fixture.componentInstance;
// foodDataService = TestBed.get(FoodDataService);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
I've run into missing <router-outlet> messages in other unit tests, but just to have a nice isolated example, I created an AuthGuard that checks if a user is logged in for certain actions.
This is the code:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (!this.authService.isLoggedIn()) {
this.router.navigate(['/login']);
return false;
}
return true;
}
Now I want to write a unit test for this.
This is how I start my test:
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([
{
path: 'login',
component: DummyComponent
}
])
],
declarations: [
DummyComponent
],
providers: [
AuthGuardService,
{
provide: AuthService,
useClass: MockAuthService
}
]
});
});
I created a DummyComponent that does nothing.
Now my test. Pretend that the service returns false and that it triggers this.router.navigate(['/login']):
it('should not let users pass when not logged in', (): void => {
expect(authGuardService.canActivate(<any>{}, <any>{})).toBe(false);
});
This will throw an exception with "Cannot find primary outlet to load".
Obviously I can use toThrow() instead of toBe(false), but that doesn't seem like a very sensible solution. Since I'm testing a service here, there is no template where I can put the <router-outlet> tag. I could mock the router and make my own navigate function, but then what's the point of RouterTestingModule? Perhaps you even want to check that navigation worked.
I could mock the router and make my own navigate function, but then what's the point of RouterTestingModule? Perhaps you even want to check that navigation worked.
There's no real point. If his is just a unit test for the auth guard, then just mock and spy on the mock to check that it's navigate method was called with the login argument
let router = {
navigate: jasmine.createSpy('navigate')
}
{ provide: Router, useValue: router }
expect(authGuardService.canActivate(<any>{}, <any>{})).toBe(false);
expect(router.navigate).toHaveBeenCalledWith(['/login']);
This is how unit tests should normally be written. To try to test any actual real navigation, that would probably fall under the umbrella of end-to-end testing.
If you want to test the router without mocking it you can just inject it into your test and then spy directly on the navigate method there. The .and.stub() will make it so the call doesn't do anything.
describe('something that navigates', () => {
it('should navigate', inject([Router], (router: Router) => {
spyOn(router, 'navigate').and.stub();
expect(authGuardService.canActivate(<any>{}, <any>{})).toBe(false);
expect(router.navigate).toHaveBeenCalledWith(['/login']);
}));
});
this worked for me
describe('navigateExample', () => {
it('navigate Example', () => {
const routerstub: Router = TestBed.get(Router);
spyOn(routerstub, 'navigate');
component.navigateExample();
});
});
it(`editTemplate() should navigate to template build module with query params`, inject(
[Router],
(router: Router) => {
let id = 25;
spyOn(router, "navigate").and.stub();
router.navigate(["/template-builder"], {
queryParams: { templateId: id }
});
expect(router.navigate).toHaveBeenCalledWith(["/template-builder"], {
queryParams: { templateId: id }
});
}
));
I came up with something like that:
describe('TestComponent', () => {
let component: TestComponent;
let router: Router;
let fixture: ComponentFixture<TestComponent>;
const routerSpy = jasmine.createSpyObj('Router', ['navigate']); // create a router spy
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
declarations: [TestComponent],
providers: [
{ provide: Router, useValue: routerSpy } // use routerSpy against Router
],
}).compileComponents();
}));
beforeEach(() => {
router = TestBed.inject(Router); // get instance of router
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it(`should navigate to 'home' page`, () => {
component.navigateToHome(); // call router.navigate
const spy = router.navigate as jasmine.Spy; // create the navigate spy
const navArgs = spy.calls.first().args[0]; // get the spy values
expect(navArgs[0]).toBe('/home');
});
});
Inspired with angular docs: https://angular.io/guide/testing-components-scenarios#routing-component
I am new to unit testing angular/javascript apps. I needed a way to mock (or spy) for my unit test. The following line is borrowed from Experimenter and helped me TREMENDOUSLY!
const routerSpy = jasmine.createSpyObj('Router', ['navigate']); // create a router spy
I would like to say that I had no idea I could do that with Jasmine. Using that line above, allowed me to then create a spy on that object and verify it was called with the correct route value.
This is a great way to do unit testing without the need to have the testbed and all the ceremony around getting the testing module setup. Its also great because it still allows me to have a fake router object with out the need to stub in all of the parameters, methods, etc etc etc.