Angular2: Can't test component with templateUrl using TestBed.compileComponent() - unit-testing

I'm trying to get the basics of Angular2 test API and TestBed.compileComponents() is driving me nuts. Either I call it like this:
beforeEach( done => {
TestBed.configureTestingModule({
declarations: [MyComponent]
})
.compileComponents().then( () => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance();
});
done();
});
And then my component is undefined in my test (I believe since compileComponent is async, test is run before my var component gets a value)
Either like that (as describe in documentation):
beforeEach( async(() => {
TestBed.configureTestingModule({
declarations: [MyComponent]
}))
.compileComponents();
beforeEach( () => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance();
});
But then I get the error: This test module uses the component HomeComponent which is using a "templateUrl", but they were never compiled. Please call "TestBed.compileComponents" before your test.
Can anybody help on this ?
Forget to say I use webpack and RC6

Try this:
describe('FooComponent', function () {
let fixture: ComponentFixture<FooComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [FooComponent]
});
fixture = TestBed.createComponent(FooComponent);
fixture.detectChanges();
});
You don't need asynchronicity here.

Try this:
beforeEach( async( () => {
TestBed.configureTestingModule({
declarations: [MyComponent]
})
.compileComponents().then( () => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance();
});
}));

I faced the same issue. I think the problem is in your component template. If you use some other custom components, but didn't specify them in testing module, than Angular throws that error (of course very misleading).
So, you next options:
Specify all used components in TestBed configuration
As variation of that you may stub all components by corresponding mocks.
use NO_ERRORS_SCHEMA for shallow testing as described here https://angular.io/docs/ts/latest/guide/testing.html#shallow-component-test

Just to build on the answer by Zonkil I found to get this to work I had to actually set the fixture and create the component within the test. For example:
it('test the component renders',() => {
fixture = TestBed.createComponent(AppComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
expect(comp).toBeDefined();
});

Related

Testing route navigation without an outlet in Angular

I am writing a spec for an Angular component that displays a button that will navigate to another page. The component makes use of Router::navigate() but does not itself have a router outlet. A parent component has the outlet. In my spec, the test should confirm that clicking on the button routes to the correct path.
My current (broken) spec tries to use RouterTestingModule to provide a route to a DummyComponent. When the button is clicked in the spec I get the following error:
'Unhandled Promise rejection:', 'Cannot find primary outlet to load 'DummyComponent'', '; Zone:', 'angular', '; Task:', 'Promise.then', '; Value:', Error{__zone_symbol__error: Error{originalStack: 'Error: Cannot find primary outlet to load 'DummyComponent'
Obviously I am approaching this problem in the wrong manner. What is the correct way to test router navigation when the component does not have a router outlet?
The component (pseudo-code):
#Component({
template: `
Go to the <button (click)="nextPage">next page</button>
`
})
export class ExampleComponent {
public myId = 5;
constructor(private _router: Router);
public nextPage(): void {
this._router.navigate(['/example', this.myId]);
}
}
The spec. This does not work:
const FAKE_ID = 999;
describe('ExampleComponent Test', () => {
let exampleComponent: ExampleComponent;
let fixture: ComponentFixture<ExampleComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ DummyComponent ],
imports: [
RouterTestingModule.withRoutes([
{ path: 'example/:id', component: DummyComponent }
]);
]
});
fixture = TestBed.createComponent(exampleComponent);
exampleComponent = fixture.componentInstance;
});
it('should route to example/:id', inject([Router, Location], (router: Router, location: Location) => {
fixture.detectChanges();
exampleComponent.myId = FAKE_ID;
const LINK_BUTTON = fixture.debugElement.query(By.css('button'));
LINK_BUTTON.nativeElement.dispatchEvent(new Event('click'));
expect(location.path()).toEqual('/example/' + FAKE_ID);
});
});
There needs to be an outlet (<router-outlet>) for the DummyComponent. If the DummyComponent is a route being navigated to from the ExampleComponent, then the ExampleComponent should have the outlet. You also also need to add the ExampleComponent to the declarations`
#Component({
tempalte: `
<router-outlet></router-outlet>
<button (click)="nextPage">next page</button>
`
})
class ExampleComponent{}
declarations: [ ExampleComponent, DummyComponent ]
If you want to avoid having to set up this infrastructure just to test the route being navigated to, the better option might be to just mock the Router, and just check that the navigate method is called with the correct path.
beforeEach(()=>{
TestBed.configureTestingModule({
providers: [
{
provide: Router,
useValue: { navigate: jasmine.createSpy('navigate') }
}
]
})
})
With this, you don't need to configure an routing at all, as you're using a fake Router. Then in your test
it('should route to example/:id', inject([Router], (router: Router) => {
expect(router.navigate).toHaveBeenCalledWith(['/example', FAKE_ID]);
});

Angular2 testing service mocks

I have a simple Angular2 component that consists of the following
import { Component, OnInit, Input } from '#angular/core';
import {FooterLinksService} from './footer-links.service';
import { environment } from '../../environments/environment';
#Component({
selector: 'app-footer-links',
templateUrl: './footer-links.component.html',
styleUrls: ['./footer-links.component.css'],
providers: [FooterLinksService]
})
export class FooterLinksComponent implements OnInit {
constructor(private footerLinksService: FooterLinksService) {
let footerLinks = this.footerLinksService.LoadFooterLinks();
}
}
I am trying to write the unit tests with Jasmine for this. Now I want to mock the FooterLinksService, but most of the examples I have seen involve manually writing a FooterLinksServiceMock. Is there any other approach I can use which autogenerates the mock service like NSubStitute and I provide the expected returns values from footerLinksService.LoadFooterLinks
As mentioned by #JBNizet, you could just use a spy. What you would do is get the actual service inside the test, then you can spy on a method an return any arbitrary value, when that method is called. It might look something like
describe('component: FooterLinksComponent', () => {
let fixture;
let service;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
FooterLinksComponent
],
});
fixture = TestBed.createComponent(FooterLinksComponent);
service = fixture.debugElement.injector.get(FooterLinksService);
});
it('should change message returned', () => {
spyOn(service, 'LoadFooterLinks').and.returnValue('Hello new message');
fixture.detectChanges();
expect(fixture.componentInstance.message).toBe('Hello new message');
});
});
You will need to move the code where you access the service from inside the constructor into the ngOnInit. The reason for this is because
You are using #Component.providers, so the service will not be created until the component is created.
When the component is created, the constructor is already called. So this doesn't give time for you to set up the spy. When you use ngOnInit, the ngOnInit is not called until you call fixture.detectChanges()

Angular 2 Testing - Component Instance is undefined

I have a problem in testing in Angular 2.
describe('Spinner Component', () => {
beforeEach(() => TestBed.configureTestingModule({
declarations: [SpinnerComponent]
}).compileComponents());
beforeEach(() => {
fixture = TestBed.createComponent(SpinnerComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
});
it('Should Create a Spinner Component', () => {
fixture.detectChanges();
var compiled = fixture.debugElement.nativeElement;
expect(compiled).toBeTruthy();
});
it('Should not be running', () => {
fixture.detectChanges();
expect(comp.isRunning).toBe(false);
});
});
The code above shows that the Spinner Component in the test 'Should not be running'. I do not know what causes this. I get an error in the console (Please see below). I have created the component instance on the second before each as seen in the code but it states that it is undefined when ran on the second test case. I need help. I would really appreciate it. Thanks in advance.
Error in console

Injecting Router during unit tests (rc.5, Router 3.0.0-rc.1)

I'm trying to unit test a component that uses the Router, my code is in typescript. There are numerous recipies for injecting Router in a test spec, but none work for me, and some are not usable in the current version. I tried this:
beforeEach(() => {
addProviders([
MyComponent,
provideRouter([]),
provide(APP_BASE_HREF, { useValue: '/' }),
provide(ActivatedRoute, { useValue: {} })
]);
});
and got the error message
Error: Bootstrap at least one component before injecting Router.
When I try to mock the Router altogether:
class MockRouter {
public navigate() {}
}
beforeEach(() => {
addProviders([
MyComponent,
provide(Router, { useClass: MockRouter })
]);
});
the test suite stops altogether with the error message
TypeError: Attempting to configurable attribute of unconfigurable property.
In router_testing_module.d.ts, they suggest this:
beforeEach(() => {
configureModule({
modules: [RouterTestingModule],
providers: [provideRoutes(
[{path: '', component: BlankCmp}, {path: 'simple', component: SimpleCmp}])]
});
});
But the function configureModule does not seem to exist (yet? anymore?).
What can I do in this situation?

Unit test for a component with dependency which has dependencies, what's wrong?

I have annoying error that probably by my mistake I cannot resolve.
I have on simple component which is actually nothing else than a top-bar element in my web application.
This component as you can see has only one dependency, the UserService and it uses it quite simply:
import { Component, OnInit } from '#angular/core';
import { MdButton } from '#angular2-material/button';
import { MdIcon , MdIconRegistry} from '#angular2-material/icon';
import { UserService } from '../services/index';
import { RouteConfig, ROUTER_DIRECTIVES, Router, ROUTER_PROVIDERS
} from '#angular/router-deprecated';
#Component({
moduleId: module.id,
selector: 'top-bar',
templateUrl: 'top-bar.component.html',
styleUrls: ['top-bar.component.css'],
providers: [MdIconRegistry, UserService, ROUTER_PROVIDERS],
directives: [MdButton, MdIcon, ROUTER_DIRECTIVES]
})
export class TopBarComponent implements OnInit {
constructor(private userService: UserService) {
this.userService = userService;
}
ngOnInit() {
}
/**
* Call UserService and logout() method
*/
logout() {
this.userService.logout();
}
}
As this service has also some dependencies (router etc) I had to provide them at the beforeEachProviders method as you can see:
import {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
} from '#angular/core/testing';
import { TopBarComponent } from './top-bar.component';
import {
Router, RootRouter, RouteRegistry, ROUTER_PRIMARY_COMPONENT
} from '#angular/router-deprecated';
import { provide } from '#angular/core';
import { SpyLocation } from '#angular/common/testing';
import { UserService } from '../services/index';
describe('Component: TopBar', () => {
beforeEachProviders(() => [
RouteRegistry,
provide(Location, { useClass: SpyLocation }),
provide(ROUTER_PRIMARY_COMPONENT, { useValue: TopBarComponent }),
provide(Router, { useClass: RootRouter }),
UserService,
TopBarComponent
]);
it('should inject the component', inject([TopBarComponent],
(component: TopBarComponent) => {
expect(component).toBeTruthy();
}));
});
When I run the test though I get this error message:
Chrome 51.0.2704 (Mac OS X 10.11.5) Component: TopBar should inject the component FAILED
Error: No provider for Location! (TopBarComponent -> UserService -> Router -> Location)
Error: DI Exception[......]
First of all as you can see the Location provider is provided.
And secondary, why my test requires to provide (or inject) also the dependencies of the used into the tested component service?
For example if from the above test I remove the Router the even that my component doesn't use Router I'll get an error because the used service does. Then shouldn't I received the same error in the component and not only in the test?
UPDATE - CHANGE OF CODE & ERROR MESSAGE
I have manage to stop getting this error by changing my spec doe to this:
import {
beforeEach,
describe,
expect,
it,
} from '#angular/core/testing';
import { TopBarComponent } from './top-bar.component';
import { UserService } from '../services/index';
import {
Router
} from '#angular/router-deprecated';
import { Http } from '#angular/http';
import { AuthHttp } from 'angular2-jwt';
describe('Component: TopBar', () => {
let router: any = Router;
let authHttp: any = AuthHttp;
let http: any = Http;
let component: TopBarComponent;
let service: UserService = new UserService(router, authHttp, http);
beforeEach(() => {
component = new TopBarComponent(service);
});
it('logout function should work ', () => {
let logout = component.logout;
logout();
expect(localStorage.getItem('token')).toBe(null);
});
});
But know I'm getting this error from my component:
TypeError: Cannot read property 'userService' of undefined
The mentioned error is on this function:
logout() {
this.userService.logout();
}
of my component but this is only on the test. In app it works normally. The function cannot reach constructor's parameter for some reason in my test.
kind of stack here...
By your code I understand that you are trying to test the topbar component.
Top bar component has a dependency on UserService.
So to answer your question, Angular does dependency injection when you run your application because all the providers are configured in the module file. But when you try to test the code in spec file you have to configure the testbed with all providers, components in the beforeEach method that are going to be used and angular leaves all the responsibility of resolving the dependency to the user as testbed acts as environment to run your code.
In your code you can do something like this
let service: UserService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [UserService, any other service on which user service is dependent] });
});
Here you can see TestBed.configureTestingModule method creates a dummy module to aid in running ur test case.
My suggestion will be create a mock UserService which doesn't have any other dependencies like the original one and assign it in the provider
Something like this
export MockUserService {
Put all essential methods stub here.
}
let service: UserService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provide: UserService, useClass: MockUserService] });
});
Then just test the topBar component's usecases.
Try creating the object of service inside beforeEach using TestBed.get(UserService). This code will automatically resolve the dependencies and create that object for use.
Remove '= new UserService(router, authHttp, http);' from 'let service: UserService = new UserService(router, authHttp, http);'