[Angular version: 2.4.5]
I'm attempting to write an Angular integration component unit test that contains a service that leverages the built-in Angular HTTP library. I cannot get my Service class to instantiate though: either the Service is undefined or I get "Error: Cannot resolve all parameters for 'RequestOptions'(?)".
I understand that typically you'd mock the backend (have successfully done that in other unit tests) or use Protractor. Other SO questions reference Angular 1 or have answers that just don't seem to work. Starting code:
#Injectable()
export class ProjectService {
projectFeature: IProjectFeature;
constructor(private http: Http) { }
getProjects(): Observable<IProjects> {
return this.http.get("/api/Projects")
.map((response: Response) => response.json() || {})
.catch(this.handleError);
}
}
// Jasmine spec file:
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
Http,
ProjectService, // service that leverages HTTP
ConnectionBackend
//RequestOptions // removed b/c otherwise can't find parameters error happens
],
imports: [
HttpModule
]
});
}));
it('sample test', async(() => {
var projectService = TestBed.get(ProjectService);
var comp = new ProjectComponent(projectService);
comp.ngOnInit(); // internally, calls projectService.getProjects()
expect(comp.projects[0].name).toEqual("Sample Project Name");
}));
HttpModule module should be provided in TestBed module configuration. After that, Http performs real XHR requests, no extra actions needed, a demo:
beforeEach(() => {
TestBed.resetTestEnvironment();
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
TestBed.configureTestingModule({
imports: [
HttpModule
]
});
});
it('should do XHR', async(inject([Http], (http) => {
http.get('test.json').subscribe(res => {
expect(res.json()).toBe(1);
});
})));
Related
I am creating a service in Angular 2 which should call an API url to retrieve its data.
For unit testing I would like to verify that the service actually calls the API. To do this TDD, the service getPicture method is currently empty:
#Injectable()
export class ImageService {
getPicture(id: string) {
//To be implemented later
}
}
My test is as follows:
describe('ImageService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [
ImageService,
{ provide: XHRBackend, useClass: MockBackend }
]
});
});
afterEach(inject([XHRBackend], (mockBackend) => mockBackend.verifyNoPendingRequests()));
describe('getPicture', () => {
it('should call picture API', inject([ImageService, XHRBackend], (service: ImageService, mockBackend) => {
mockBackend.connections.subscribe((connection) => {
expect(connection.request.url).toBe('localhost/some/url/picture');
});
service.getPicture('id');
}));
});
});
Wherever I look, I see code to deal with any incoming requests. However, I can't find anything that will cause a failed test when no request is made.
I could set some variable (request received) to false, set it true in the subscribe block but this feels clumsy and something that should have a proper implementation already.
How can I verify that the/any http request was made?
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.
I am having a problem creating a unit test for my translate service. I have this as a constructor of my TranslateService
constructor(#Inject(TRANSLATIONS) private _translations: any) {}
I created my translate.service.spec.ts with this
describe('On initialize', () => {
it('No changes made to the service', async(inject([TranslateService], (service: TranslateService) => {
let translate = TestBed.get(TranslateService);
})));
});
However, the console prompts me with an error saying, "No provider for TranslateService". How inject the #Inject in the spec file?
If you using this
TestBed.configureTestingModule({
providers: [
{ provide: TRANSLATION, useClass: TranslationService }
]
});
Then you need to do this
it('should inject', inject([TRANSLATION], (value: TranslationService) => {
}));
The provide specifies the token for which we can inject. Since the token isn't TranslationService, we can't inject TranslationService. We need to inject by the token TRANSLATION
UPDATE
Is the inject is in the TranslationService
class TransactionService {
constructor(#Inject(TRANSLATION) value) {}
}
Then you should probably do something more like
TestBed.configureTestingModule({
providers: [
TranslationsService,
{ provide: TRANSLATION, useValue: whateverTranslationIs }
]
});
it('should inject', inject([TranslationService], (value: TranslationService) => {
}));
You need to configure the TRANSACTIONS injectable in the test bed configuration
I have a question in creating specs/unit tests in Angular2. Whenever you are injecting a mocked service, when do you use the inject function as the one below
it('function that the component calls',
inject([MyService], (service: MyService) => { // ...
}));
Or when do you use it as the one below
beforeEach(() => {
let myMockService = new MyMockService();
TestBed.configureTestingModule({
providers: [
{ provide: MyService, useValue: myMockService }
]
});
TestBed.overrideComponent(MyComponent, {
set: {
providers: [
{ provide: MyService, useValue: myMockService }
]
}
});
})
Can someone enlighten me on the matter?
The main reason to use it is to get access to the service in your test, when Angular is creating it. For instance
providers: [ MyService ]
Here Angular is creating it, and you only have access to it through Angular's injector.
But you're providing the service as a value then there is no need to use inject as you already have access to it
let serviceInstance = new Service();
provider: [ { provide: MyService, useValue: serviceInstance } ]
Here you already have access to serviceInstance so no need to get it from the injector.
Also if you don't need to access to the service inside the test, then there's not even a need to try and access it. But sometimes your mock will have thing you want to do with it inside the test.
Aside from inject, there are only ways to access the service
You could...
For your particular example you don't need inject at all. You just need to move the mock outside the scope of the beforeEach so that the it can use it
let myMockService = new MyMockService();
beforeEach(() => {
})
it('function that the component calls', () => {
myMockService.doSomething();
}));
You could...
Instead of using inject, you could get it from the TestBed, which acts like an injector. Maybe this is preferred as you can add it in your beforeEach
let service;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ MyService ]
});
service = TestBed.get(MyService);
})
it('function that the component calls', () => {
service.doSomething();
}));
You could...
Get it from the DebugElement which also acts like an injector
it('function that the component calls', () => {
let fixture = TestBed.createComponent(TestComponent);
let service = fixture.debugElement.get(MyService);
}));
So it's really a matter of preference. I personally try to stop using inject, as there are the other, less verbose options.
You use inject() when you want to get instances from the provide passed into your test code (for example the HTTP MockBackend) or any other service you want to communicate to directly in your test code.
TestBed.configureXxx only sets up providers for the test component.