This time, AuthService has been implemented as a pair of getters and setters.
auth.service.ts
import{Injectable}from'@angular/core';
@Injectable()
exportclassAuthService{
private authenticated =false;
publicgetisAuthenticated():boolean{
returnthis.authenticated;
}
publicsetisAuthenticated(value:boolean):void{
this.authenticated= value;
}
}
Our test is:
describe('AccessControlService',()=>{
let service:AccessControlService;
beforeEach(()=>{
service =getService(AccessControlService);
});
it('redirects to logout when trying to access the lobby while authenticated',()=>{
// TODO mock that the current state is authenticated
const result = service.checkAccess('lobby');
expect(result).toBe(false);
});
});
Notice that we have not defined the behavior of the getter AuthService.isAuthenticated yet. Therefore, it is no surprise that we get an error when this test is run.
We present an alternative way to achieve the same result. Depending on the context, you may find one technique better suited than the other for your current situation.
it('redirects to logout when trying to access the lobby while authenticated',()=>{
isAuthenticated =true;// Override the default value
const result = service.checkAccess('lobby');
expect(result).toBe(false);
});
});
caution
Remember to initialize all variables in beforeEach and not in their declaration statement. Otherwise some test may get flaky as values from other tests start leaking.
And the service we would like to test is subscribing to this observable in its constructor.
@Injectable()
exportclassMyService{
constructor(
readonly router: Router,
readonly authService: AuthService){
this.authService.authenticated$.subscribe(auth=>{
if(!auth){
this.router.navigate(['/home']);
}
});
}
}
We have to define the behavior of authenticated$ before the service gets created. Otherwise OmniMock won't know what to do when authService is used.
Fortunately, we can call getMock before creating the actual service or component.
We could mock the rxjs API, expect a call to subscribe, register the callback and then invoke the callback. But this would be a lot of work.
Instead, we can simply use rxjs in our test. This is a slight deviation to the true test in a vacuum philosophy, but we can make an exception here because:
rxjs is a single-purpose code utility library and we do not use it for I/O in this context.
The class we are testing remains entirely isolated from all other code in our project.
We trust rxjs to be sufficiently tested and of high quality such that it won't break our tests.
rxjs is not used as a gateway to some global API or global state. Everything remains local to our test.
The last affirmation is not entirely true. rxjs might be using setTimeout or a similar I/O method to defer work internally. We must be careful to wrap everything in a fakeAsync context and call flush() such that this implementation detail doesn't interfere with our test.