I'm still developing an app based on the Angular2 Heroes tutorial. At this point I have a component where the user makes an edit, clicks on Save and on success the user is spirited to the parent page (route "../"). If there is an error in saving then the routing doesn't occur, and the page displays the error information.
The spiriting occurs in the component's save function:
private gotoParent(): void {
this.router.navigate(['../'], { relativeTo: this.route });
}
public save(): void {
this.error = null;
let that = this;
this.orgService
.save(that.org)
.subscribe(
(org: Org): void => {
that.org = org;
that.savedOrg = new Org(that.org);
that.gotoParent();
},
error => this.error = error
);
}
The test I have so far is:
routeStub = { data: Observable.of( { org: org1 } ), snapshot: {} };
TestBed.configureTestingModule({
imports: [ FormsModule, RouterTestingModule ],
providers : [
{ provide: DialogService, useClass: MockDialogService },
{ provide: GlobalsService, useClass: MockGlobalsService },
{ provide: OrgService, useClass: MockOrgService },
{ provide: ActivatedRoute, useValue: routeStub }
],
declarations: [ OrgDetailComponent ],
})
.compileComponents();
}));
...
it('responds to the Save click by saving the Org and refilling the component', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
comp = fixture.componentInstance;
comp.org = new Org(org1);
comp.org.id = 2;
comp.org.name = 'Another Org';
let elButton = fixture.debugElement.query(By.css('#save'));
elButton.nativeElement.click();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(comp.error).toBeNull();
expect(comp.savedOrg.id).toEqual(2);
expect(comp.savedOrg.name).toEqual('Another Org');
expect(routeStub).toHaveBeenCalledWith(['../']);
});
});
}));
When the expect(routeStub) is called I get "Error: expected a spy, but got Object ...".
Most tutorials regarding testing routing set up a routing table and test that. I'm not sure if I need a route class (replacing ActivatedRoute?) or not.
Thanks,
Jerome.
UPDATE on 3/25
The answer by snorkpete, and in other threads by peeskillet, aren't solving my issues. I think that this is because I've two different things going on in my code, and I've shared only one here.
My component has an ngOnInit() that relies on a resolver to deliver data to the subscribe() within the ngOnInit(). In my tests this is provided by the (renamed) activatedRouteStub instance:
activatedRouteStub = { data: Observable.of( { org: org1 } ) }
In testing the ngOnInit() I get the provided Org object just fine.
Now I need to also process a Save button, which also causes the browser to display the parent page. The component calls:
this.router.navigate(['../'], {relativeTo: this.route});
If I remove the activatedRouteStub, replacing it with a routerStub, everything breaks.
If I use both activatedRouteStub and routerStub, the call
expect(routerStub.navigate).toHaveBeenCalled()
fails, complaining about expecting a spy and getting an Object.
If I add the navigate: jasmineCreateSpy('navigate') to the activatedRouteStub and do the expect() on activatedRouteStub.navigate() I'm told that that wasn't navigated against.
I'm puzzled.
Jerome.
SOLUTION ON 3/25, 17:00 CDT
Thanks to prior help by peeskillet and immediate help from snorkpete I've an answer to my issues.
I happen to need both an ActivatedRoute and a router. What is more, when I call toHaveBeenCalledWith() I need to provide ALL of what the this.router.navigate() call was provided. A "DUH" obvervation on my part, but not realizing it wasted tons of my time.
To get a complete solution into one place, here is pertinent code for my component and its test spec.
For the component:
public ngOnInit(): void {
this.error = null;
this.stateOptions = this.globalsService.getStateOptions();
let that = this;
this.route.data
.subscribe((data: { org: Org }) => {
that.org = data.org;
that.savedOrg = new Org(that.org);
});
}
private gotoParent(): void {
this.router.navigate(['../'], { relativeTo: this.route });
}
public save(): void {
this.error = null;
let that = this;
this.orgService
.save(that.org)
.subscribe(
(org: Org): void => {
that.org = org;
that.savedOrg = new Org(that.org);
that.gotoParent();
},
error => this.error = error
);
}
Note that goToParent() uses a route string and a relativeTo: parameter.
In the testing:
@Injectable()
export class MockActivatedRoute {
constructor() { }
data: Observable<Org> = null;
}
@Injectable()
export class MockRouter {
constructor() { }
navigate: any = () => {};
snapshot: any = {};
}
describe("...", () => {
...
let router: Router = null;
let activatedRoute: ActivatedRoute = null;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ FormsModule, RouterTestingModule ],
providers : [
{ provide: DialogService, useClass: MockDialogService }, // don't worry about these three in this example...
{ provide: GlobalsService, useClass: MockGlobalsService },
{ provide: OrgService, useClass: MockOrgService },
{ provide: Router, useClass: MockRouter },
{ provide: ActivatedRoute, useClass: MockActivatedRoute }
],
declarations: [ OrgDetailComponent ],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(OrgDetailComponent);
dialogService = fixture.debugElement.injector.get(DialogService);
globalsService = fixture.debugElement.injector.get(GlobalsService);
orgService = fixture.debugElement.injector.get(OrgService);
router = fixture.debugElement.injector.get(Router);
activatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
});
it('responds to the Save click by saving the Org and refilling the component', async(() => {
activatedRoute.data = Observable.of( { org: org1 } ); // The org1 is an instance of Org.
let spy = spyOn(router, 'navigate');
fixture.detectChanges();
fixture.whenStable().then(() => {
comp = fixture.componentInstance;
comp.org = new Org(org1);
comp.org.id = 2;
comp.org.name = 'Another Org';
let elButton = fixture.debugElement.query(By.css('#save'));
elButton.triggerEventHandler('click', null);
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(comp.error).toBeNull();
expect(comp.savedOrg.id).toEqual(2);
expect(comp.savedOrg.name).toEqual('Another Org');
expect(router.navigate).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith(['../'], { relativeTo: activatedRoute });
});
});
}));
});