I have a component that uses the Apollo Angular client to fetch some data. I'm trying to write tests for it to check how it responds to different types of data.
However there does seem to be a way to change the response data after the first time (I'm assuming due to caching). Is there a way to clear the cache while testing or force it to not use the cache?
My code looks something like this
describe('TestComponent', () => {
let apollo: ApolloTestingController;
let component: UserDetailsComponent;
let fixture: ComponentFixture<UserDetailsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ApolloTestingModule,
],
declarations: [TestComponent],
})
.compileComponents();
apollo = TestBed.get(ApolloTestingController);
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
}));
it('should work with data type 1', () => {
apollo.expectOne('TestQuery').flush({
data: {
something: 'test 1',
},
});
});
it('should work with data type 2', () => {
// Fails to match here
apollo.expectOne('TestQuery').flush({
data: {
something: 'test 2',
},
});
});
});
I found that you can do something like this
const apolloClient = TestBed.get(Apollo).getClient();
await apolloClient.clearStore();
Related
I'm writting unit test for angular2 - karma - jasmine.
I used mockBackend to response data when service call API.
This is my code
function Connection(mockBackend) {
connection.mockRespond(new Response(
new ResponseOptions({
body: JSON.stringify(lookup)
}
)));
});
}
describe('Update Contact Details Test', () => {
let fixture, comp;
beforeEach(async(inject([MockBackend], (mockBackend: MockBackend) => {
Connection(mockBackend);
TestBed.configureTestingModule({
declarations: [ ContactFieldListComponent ],
providers: _.union(DEFAULT_PROVIDERS, [FieldService, LookupsService, ContactService]),
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: DEFAULT_IMPORT
})
.compileComponents().then(() => {
fixture = TestBed.createComponent(ContactFieldListComponent);
comp = fixture.componentInstance;
});
})));
it('get contact field list', () =>
expect(1).toBe(1);
});
});
It don't work with error Error: No provider for MockBackend!.
How can I resolve it?
It looks like you missed to add the MockBackend into the providers of the TestBed.
...
TestBed.configureTestingModule({
declarations: [ ContactFieldListComponent ],
providers: _.union(
DEFAULT_PROVIDERS, [
FieldService,
LookupsService,
ContactService,
MockBackend // <- add it here
]),
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: DEFAULT_IMPORT
})
...
Additionally I think you have to create another beforeEach under the one with the TestBed, because in the first one, MockBackend isn't registered.
I have a sample test for you, that perhaps can help you: https://github.com/angular-workshops/angular2-testing/blob/solution/tour-of-heroes/src/app/hero.service/hero.service.shallow.spec.ts
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.
I have moved from RC4 to final release (2.1.0) and I am refactoring my unit tests to conform to 2.1.0 syntax. This is easy except for HTTP mocking.
I cannot find any examples for how to mock HTTP requests in 2.1.0
Here is an RC4 HTTP unit test. How would I re-write this in final release 2.1.0?
it('ngOnInit()',
async(inject([TestComponentBuilder, XHRBackend], (tcb:TestComponentBuilder, mockBackend:MockBackend) => {
tcb.createAsync(Route1ListComponent).then((fix:ComponentFixture<Route1ListComponent>) => {
// THIS BLOCK OF CODE I NEED HELP TO RE-WRITE TO 2.1.0
mockBackend.connections.subscribe(
(connection:MockConnection) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: persons
}
)));
});
// THIS BLOCK OF CODE WILL NOT CHANGE
let instance = fix.componentInstance;
instance.ngOnInit();
expect(instance.persons.length).toBe(3);
});
})));
NOTE: Do NOT provide RC code please. Thanks
First thing you need to do is configure the TestBed. There's no more TestComponentBuilder. With the TestBed, it's like configuring an #NgModule from scratch, just for the test environment. This means you will add the component under test to the declarations, add any providers to the provider, and any imports to the imports.
To configure the mock backend for Http provider, you would just create the Http from the MockBackend.
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpModule ],
declarations: [ RouteListComponent ],
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend: MockBackend, options: BaseRequestOptions) => {
return new Http(backend, options);
},
deps: [ MockBackend, BaseRequestOptions ]
}
]
})
})
That should be it for the configuration, assuming you don't need any other providers or imports I don't know about.
For the test you'll first want to make it an async test, as you'll be doing asynchronous operations in the test. This hasn't changed from RC, you just use async. If the component uses templateUrl (and you're not using Webpack), then you will need to call TestBed.compileComponents(), otherwise no need to. After that you can create the component with TestBed.createComponent
let fixture: ComponentFixture<RouteListComponent>;
let component: RouteListComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({ ... })
.compileComponents().then(() => {
fixture = TestBed.createComponent(RouteListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
}));
it('...', async(inject([MockBackend], (backend: MockBackend) => {
})))
Pretty much all the things above related to testing can be imported from #angular/core/testing. Your use of the MockBackend would still be the same.
Another note, you don't need to call the component.ngOnInit. That is called by the framework when you call fixture.detectChanges()
See Also:
Testing documentation for more complete examination of testing support.
Many thanks to #peeskillet for helping me reach my answer ..
import {APP_BASE_HREF} from '#angular/common';
import {async, ComponentFixture, TestBed} from '#angular/core/testing';
import {By} from '#angular/platform-browser';
import {AppModule} from '../../../app.module';
import {persons} from '../../../data/persons';
import {Route1ListComponent} from './route1-list.component';
// HTTP mocking imports
import {BaseRequestOptions, Http, Response, ResponseOptions} from '#angular/http';
import {MockBackend, MockConnection} from '#angular/http/testing';
describe('route1-list.component.ts', () => {
let fix: ComponentFixture<Route1ListComponent>;
let instance: Route1ListComponent;
let injector: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [AppModule],
providers: [{provide: APP_BASE_HREF, useValue: '/'},
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (pBackend: MockBackend, pOptions: BaseRequestOptions) => {
return new Http(pBackend, pOptions);
},
deps: [MockBackend, BaseRequestOptions]
}]
}).compileComponents()
.then(() => {
fix = TestBed.createComponent(Route1ListComponent);
instance = fix.componentInstance;
injector = fix.debugElement.injector;
});
}));
it('should instantiate component', () => {
expect(instance).toEqual(jasmine.any(Route1ListComponent));
});
it('should have expected text', () => {
let el = fix.debugElement.query(By.css('section.route1-list')).nativeElement;
expect(el.textContent).toMatch(/route 1 list view/i, 'should have expected text');
});
it('ngOnInit()', async(() => {
let backend = injector.get(MockBackend);
backend.connections.subscribe(
(connection: MockConnection) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: persons
}
)));
});
fix.detectChanges(); // Calls instance.ngOnInit()
expect(instance.persons.length).toBe(3);
}));
it('ngOnInit() failure', async(() => {
let backend = injector.get(MockBackend);
backend.connections.subscribe(
(connection: MockConnection) => {
connection.mockError(new Error('error'));
});
fix.detectChanges(); // Calls instance.ngOnInit()
expect(instance.persons).toBeUndefined();
}));
});
Notice that, at the time of writing, the Angular2 docs at ..
https://angular.io/docs/ts/latest/api/http/testing/index/MockBackend-class.html
https://angular.io/docs/ts/latest/api/http/testing/index/MockConnection-class.html
seem to be wrong.
When I use Injector.resolveAndCreate as detailed in the docs I get the error:
Property 'resolveAndCreate' does not exist on type 'typeof Injector'.
To fix it I had to base my answer on the answer provided by #peeskillet
I am implementing a wizard component in Angular 2 RC4, and now I am trying to write som unit tests. Unit testing in Angular 2 is starting to get well documented, but I simply cannot find out how to mock the result of a content query in the component.
The app has 2 components (in addition to the app component), WizardComponent and WizardStepComponent. The app component (app.ts) defines the wizard and the steps in its template:
<div>
<fa-wizard>
<fa-wizard-step stepTitle="First step">step 1 content</fa-wizard-step>
<fa-wizard-step stepTitle="Second step">step 2 content</fa-wizard-step>
<fa-wizard-step stepTitle="Third step">step 3 content</fa-wizard-step>
</fa-wizard>
</div>
The WizardComponent (wizard-component.ts) gets a reference to the steps by using a ContentChildren query.
#Component({
selector: 'fa-wizard',
template: `<div *ngFor="let step of steps">
<ng-content></ng-content>
</div>
<div><button (click)="cycleSteps()">Cycle steps</button></div>`
})
export class WizardComponent implements AfterContentInit {
#ContentChildren(WizardStepComponent) steps: QueryList<WizardStepComponent>;
....
}
The problem is how to mock the steps variable in the unit test:
describe('Wizard component', () => {
it('should set first step active on init', async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb
.createAsync(WizardComponent)
.then( (fixture) =>{
let nativeElement = fixture.nativeElement;
let testComponent: WizardComponent = fixture.componentInstance;
//how to initialize testComponent.steps with mock data?
fixture.detectChanges();
expect(fixture.componentInstance.steps[0].active).toBe(true);
});
})));
});
I have created a plunker implementing a very simple wizard demonstrating the problem. The wizard-component.spec.ts file contains the unit test.
If anyone can point me in the right direction, I would greatly appreciate it.
Thanks to drewmoore's answer in this question, I have been able to get this working.
The key is to create a wrapper component for testing, which specifies the wizard and the wizard steps in it's template. Angular will then do the content query for you and populate the variable.
Edit: Implementation is for Angular 6.0.0-beta.3
My full test implementation looks like this:
//We need to wrap the WizardComponent in this component when testing, to have the wizard steps initialized
#Component({
selector: 'test-cmp',
template: `<fa-wizard>
<fa-wizard-step stepTitle="step1"></fa-wizard-step>
<fa-wizard-step stepTitle="step2"></fa-wizard-step>
</fa-wizard>`,
})
class TestWrapperComponent { }
describe('Wizard component', () => {
let component: WizardComponent;
let fixture: ComponentFixture<TestWrapperComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
schemas: [ NO_ERRORS_SCHEMA ],
declarations: [
TestWrapperComponent,
WizardComponent,
WizardStepComponent
],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestWrapperComponent);
component = fixture.debugElement.children[0].componentInstance;
});
it('should set first step active on init', () => {
expect(component.steps[0].active).toBe(true);
expect(component.steps.length).toBe(3);
});
});
If you have better/other solutions, you are very welcome to add you answer as well. I'll leave the question open for some time.
For anybody coming to this question recently, things have changed slightly and there is a different way to do this, which I find a bit easier. It is different because it uses a template reference and #ViewChild to access the component under test rather than fixture.debugElement.children[0].componentInstance. Also, the syntax has changed.
Let's say we have a select component that requires an option template to be passed in. And we want to test that our ngAfterContentInit method throws an error if that option template is not provided.
Here is a minimal version of that component:
#Component({
selector: 'my-select',
template: `
<div>
<ng-template
*ngFor="let option of options"
[ngTemplateOutlet]="optionTemplate"
[ngOutletContext]="{$implicit: option}">
</ng-template>
</div>
`
})
export class MySelectComponent<T> implements AfterContentInit {
#Input() options: T[];
#ContentChild('option') optionTemplate: TemplateRef<any>;
ngAfterContentInit() {
if (!this.optionTemplate) {
throw new Error('Missing option template!');
}
}
}
First, create a WrapperComponent which contains the component under test, like so:
#Component({
template: `
<my-select [options]="[1, 2, 3]">
<ng-template #option let-number>
<p>{{ number }}</p>
</ng-template>
</my-select>
`
})
class WrapperComponent {
#ViewChild(MySelectComponent) mySelect: MySelectComponent<number>;
}
Note the use of the #ViewChild decorator in the test component. That gives access to MySelectComponent by name as a property on the TestComponent class. Then in the test setup, declare both the TestComponent and the MySelectComponent.
describe('MySelectComponent', () => {
let component: MySelectComponent<number>;
let fixture: ComponentFixture<WrapperComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
/*
Declare both the TestComponent and the component you want to
test.
*/
declarations: [
TestComponent,
MySelectComponent
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WrapperComponent);
/*
Access the component you really want to test via the
ElementRef property on the WrapperComponent.
*/
component = fixture.componentInstance.mySelect;
});
/*
Then test the component as normal.
*/
describe('ngAfterContentInit', () => {
component.optionTemplate = undefined;
expect(() => component.ngAfterContentInit())
.toThrowError('Missing option template!');
});
});
#Component({
selector: 'test-cmp',
template: `<wizard>
<wizard-step [title]="'step1'"></wizard-step>
<wizard-step [title]="'step2'"></wizard-step>
<wizard-step [title]="'step3'"></wizard-step>
</wizard>`,
})
class TestWrapperComponent {
}
describe('Wizard Component', () => {
let component: WizardComponent;
let fixture: ComponentFixture<TestWrapperComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [SharedModule],
schemas: [NO_ERRORS_SCHEMA],
declarations: [TestWrapperComponent]
});
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestWrapperComponent);
component = fixture.debugElement.children[0].componentInstance;
fixture.detectChanges();
});
describe('Wizard component', () => {
it('Should create wizard', () => {
expect(component).toBeTruthy();
});
});
});
I have a simple angular2 component as defined below. And I'm looking to create a unit testing with karma, jasmine to run through this component.
#Component({
selector: 'property',
template: require('./property.component.html'),
directives: [Panel],
providers: [ConfigService]});
export class PropertyComponent {
config:any;
constructor(config:ConfigService) {
this.config = config.getConfig();
}
}
This is my testing spec file.
describe('property component', () => {
it('should have property page title', injectAsync([TestComponentBuilder], (tcb) => {
return tcb.createAsync(PropertyComponent).then((fixture) => {
let propertyComp = fixture.componentInstance,
element = fixture.nativeElement;
expect(element.querySelector('h1').innerText).toBe('property page');
});
}));
})
However I got a list of weird errors... I'm guessing this is due to the ConfigService Provider in the PropertyComponent, because when I removed the provider dependency, it went through.
Does anyone know how to deal with the dependency Providers?
Thanks!
errors:
_instantiateProvider#angular2-seed/config/spec-bundle.js:23435:38
_new#angular2-seed/config/spec-bundle.js:23424:42
getObjByKeyId#angular2-seed/config/spec-bundle.js:22937:38
_getByKeyDefault#angular2-seed/config/spec-bundle.js:23641:51
_getByKey#angular2-seed/config/spec-bundle.js:23587:42
_getByDependency#angular2-seed/config/spec-bundle.js:23573:35
_instantiate#angular2-seed/config/spec-bundle.js:23463:53
_instantiateProvider#angular2-seed/config/spec-bundle.js:23435:38
_new#angular2-seed/config/spec-bundle.js:23424:42
instantiateProvider#angular2-seed/config/spec-bundle.js:22924:35
init#angular2-seed/config/spec-bundle.js:34694:44
AppElement#angular2-seed/config/spec-bundle.js:34371:33
viewFactory_HostPropertyComponent0
createRootHostView#angular2-seed/config/spec-bundle.js:35741:48
You need to use beforeEachProviders in this case:
import {beforeEachProviders, describe, it, expect} from 'angular2/testing';
//...other imports...
describe('property component', () => {
beforeEachProviders(()=> [
ConfigService, //if you don't need to mock
provide(ConfigService, {useClass:MockConfigService}) // more typical
]);
it('should have property page title', injectAsync([TestComponentBuilder], (tcb) => {
return tcb.createAsync(PropertyComponent).then((fixture) => {
//expectations...
});
}));
})
Note that you need to import angular's patched describe, it, expect functions along with beforeEachProvidersfrom angular2/testing. I emphasize this because it's easy to forget to do that, and it results in failures with rather unintuitive messages.