问题
I am trying to write a unit test for one of the components named Login.Component.ts whose code is given below. I tried various approaches but unable to get a successful response. Before explaining the component and service code, I want to know –
TestBed– My understanding was that testbed in angular initiates and setup all the boilerplate code (required for testing like creating view, initializing and injecting service, creating routers and all this only for testing environment) by itself and all we need to do is to write some minimal code like below –beforeEach(() => { TestBed.configureTestingModule({ imports:[RouterTestingModule.withRoutes([])], declarations: [LoginComponent], providers: [AuthenticationService] }) .compileComponents(); fixture = TestBed.createComponent(LoginComponent); component = fixture.componentInstance; authenticationservice = TestBed.get(AuthenticationService);});
But looks like my understanding is wrong as component and authenticationservice doesn’t look like the objects as seems from the error I am getting –
<spyOn> : could not find an object to spy upon for login() - Angular TestBed
Login.Component.ts -
@Component({
selector: 'app-login',
templateUrl: './login.component.html'
})
export class LoginComponent implements OnInit {
constructor(
private authenticationService: AuthenticationService,
private router: Router
) { }
ngOnInit() {
}
login({ formValue }: { formValue: LoginRequest }) {
this.authenticationService.login(formValue.user, formValue.password)
.subscribe(
res => {
this.router.navigate(['/']);
},
error => {
Console.log(“Login Error”);
});
}
}
Authentication.service.ts
@Injectable()
export class AuthenticationService {
constructor(private httpClient: HttpClient) { }
login(user: string, pass: string): Observable<authToken> {
return this.httpClient.post<authToken >(SomeURI+'/oauth/token', {},
{
params: new HttpParams()
.set('username', userId)
.set('password', password)
}
).do(this.htoken);
}
private htoken(token: authToken) {
//some local storage logic here
}
approutes.module.ts
const appRoutes: Routes = [
30. { path: '', component: HomeComponent, canActivate: [IsAuthenticatedActivateGuard] },
{ path: 'login', component: LoginComponent},
{ path: '**', component: NotFoundComponent, canActivate: [IsAuthenticatedActivateGuard] }
];
@NgModule({
imports: [
RouterModule.forRoot(
appRoutes
)
],
exports: [
RouterModule
]
})
export class AppRoutingModule { }
Writing Test -
Approach 1 -
//imports
fdescribe('LoginComponent', () => {
let component: LoginComponent;
let authenticationservice: AuthenticationService;
let router: Router;
let spyService: any;
let httpClient: HttpClient;
interface LoginRequest {
user: string;
password: string;
}
let creds: LoginRequest = {
user: "username",
password: "userpasswd"
}
let m: MenuConfig = {
}
let oToken: OauthToken = {
access_token: "abc",
token_type: "jj",
refresh_token: "y",
expires_in: 10,
scope: "g",
acessibleMenuItems: m
};
beforeEach(() => {
component = new LoginComponent(authenticationservice, router, null);
authenticationservice = new AuthenticationService(httpClient);
});
fit('should login successfully', () => {
spyService = spyOn(authenticationservice, 'login').and.callFake(() => {
return Observable.of(oToken);
});
component.login({ value: creds });
expect(spyService).toHaveBeenCalledTimes(1);
})
});
Error -
TypeError: Cannot read property 'navigate' of undefined
Approach 2 – When I thought of using TestBed assuming that it will take care of everything,
//imports
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
let authenticationservice: AuthenticationService;
let router: Router;
let spyService: any;
let spyRouter: any;
let httpClient: HttpClient;
interface LoginRequest {
user: string;
password: string;
}
let creds: LoginRequest = {
user: "username",
password: "userpasswd"
}
let m: MenuConfig = {
}
let oToken: OauthToken = {
access_token: "abc",
token_type: "jj",
refresh_token: "y",
expires_in: 10,
scope: "g",
acessibleMenuItems: m
};
beforeEach(() => {
TestBed.configureTestingModule({
imports:[RouterTestingModule.withRoutes([])],
declarations: [LoginComponent],
providers: [AuthenticationService]
})
.compileComponents();
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
authenticationservice = TestBed.get(AuthenticationService);
//fixture.detectChanges();
});
it('should login successfully', () => {
spyService = spyOn(authenticationservice, 'login').and.callFake(() => {
return Observable.of(oToken);
});
//spyRouter = spyOn((<any>component).router, 'navigate').and.callThrough();
component.login({ value: creds });
expect(spyService).toHaveBeenCalledTimes(1);
//expect(spyRouter).toHaveBeenCalledTimes(1);
})
});
Error -
Error: Template parse errors:
'app-messages' is not a known element:
1. If 'app-messages' is an Angular component, then verify that it is part of this module.
2. If 'app-messages' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("<h4>Login</h4>
<hr>
[ERROR ->]<app-messages></app-messages>
<!-- Really good forms resource: https://toddmotto.com/angular-2-forms-"): ng:///DynamicTestModule/LoginComponent.html@2:0
There is no directive with "exportAs" set to "ngForm" ("nputs 'name' attribute -->
<form name="form" class="form-horizontal" novalidateautocomplete="false" [ERROR ->]#f="ngForm" (ngSubmit)="login(f)">
<div class="form-group">
<label for="user-input">User Id</la"): ng:///DynamicTestModule/LoginComponent.html@6:73
There is no directive with "exportAs" set to "ngModel" (" <input class="form-control" type="text" id="user-input" placeholder="User Id" ngModel name="user" [ERROR ->]#user="ngModel" required>
<app-input-validation-messages [model]="user"></app-input-validation-me"): ng:///DynamicTestModule/LoginComponent.html@9:102
Can't bind to 'model' since it isn't a known property of 'app-input-validation-messages'.
1. If 'app-input-validation-messages' is an Angular component and it has 'model' input, then verify that it is part of this module.
2. If 'app-input-validation-messages' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
3. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component. ("ceholder="User Id" ngModel name="user" #user="ngModel" required>
<app-input-validation-messages [ERROR ->][model]="user"></app-input-validation-messages>
</div>
<div class="form-group">
"): ng:///DynamicTestModule/LoginComponent.html@10:35
'app-input-validation-messages' is not a known element:
1. If 'app-input-validation-messages' is an Angular component, then verify that it is part of this module.
2. If 'app-input-validation-messages' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("type="text" id="user-input" placeholder="User Id" ngModel name="user" #user="ngModel" required>
[ERROR ->]<app-input-validation-messages [model]="user"></app-input-validation-messages>
And
Error: <spyOn> : could not find an object to spy upon for login()
Usage: spyOn(<object>, <methodName>)
at SpyRegistry.spyOn (http://localhost:9877/base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?da99c5b057693d025fad3d7685e1590600ca376d:4364:15)
at Env.spyOn (http://localhost:9877/base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?da99c5b057693d025fad3d7685e1590600ca376d:925:32)
at spyOn (http://localhost:9877/base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?da99c5b057693d025fad3d7685e1590600ca376d:4203:18)
No idea what am I doing wrong here. Have I messed it up?
Approach 3 - Somehow seems to be working, not sure what I did different. Yet, I am unable to figure out as how to test the error path of the component here in second test below i.e. asserting for some error message or something.
describe('LoginComponent', () => {
let component: any;
let fixture: ComponentFixture<LoginComponent>;
let authenticationservice: any;
let router: Router;
let spyService: any;
let spyRouter: any;
let httpClient: HttpClient;
interface LoginRequest {
user: string;
password: string;
}
let creds: LoginRequest = {
user: "username",
password: "userpasswd"
}
let m: Config = {
}
let oToken: authToken = {
access_token: "abc",
token_type: "jj",
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
FormsModule,
RouterTestingModule
],
declarations: [
LoginComponent
],
providers: [
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
authenticationservice = TestBed.get(AuthenticationService);
});
it('should login successfully', () => {
spyService = spyOn(authenticationservice, 'login').and.callFake(() => {
return Observable.of(oToken);
});
spyRouter = spyOn((<any>component).router, 'navigate');//.and.callThrough();
component.login({ value: creds });
expect(spyService).toHaveBeenCalledTimes(1);
expect(spyRouter).toHaveBeenCalledTimes(1);
});
it('should throw error message - bad credentials', () => {
spyService = spyOn(authenticationservice, 'login').and.returnValue(Observable.throw("Could not log in: Bad credentials"));
spyRouter = spyOn((<any>component).router, 'navigate');
var out = component.login({ value: creds });
console.log(":::::::"+out);
//how to put an assertion for Login Error Message here assuming that is returned by the catch block.
expect(spyService).toHaveBeenCalledTimes(1);
})
});
回答1:
According to the documentation :
You may also be able to get the service from the root injector via
TestBed.get(). This is easier to remember and less verbose. But it only works when Angular injects the component with the service instance in the test's root injector.
So maybe you should try using the injector :
The safest way to get the injected service, the way that always works, is to get it from the injector of the component-under-test. The component injector is a property of the fixture's DebugElement.
authenticationservice = fixture.debugElement.injector.get(AuthenticationService)
EDIT Try injecting the dependency directly into your test
it('should login successfully', inject([AuthenticationService], (service: AuthenticationService) => {...}));
来源:https://stackoverflow.com/questions/49427564/spyon-could-not-find-an-object-to-spy-upon-for-login-angular-testbed