Missing dependency: Cannot read property 'saveToken' of undefined thrown - unit-testing

I'm trying to mock an authentication service. I post my login credentials and get a token back, i then save the token.
Basically the test runs if i comment the line:
this.authenticationService.saveToken(res.json().token);
I injected the service in the test, and this line doesn't affect the output.
My error is "Cannot read property 'saveToken' of undefined thrown"
Here is my service:
private authSuccess(res: Response){
this.isAuthenticated = true;
this.authenticationService.saveToken(res.json().token);
return res.json();
}
public postLogin(loginData: LoginModel): Observable<any>{
let body = JSON.stringify(loginData);
var headers = new Headers();
headers.append('Content-Type', 'application/json');
let options = new RequestOptions({ headers: headers });
return this.http.post(this.loginUrl, body, options)
//success
.map(this.authSuccess)
//error
.catch(this.handleError);
}
Here is my test:
describe('login service tests', () => {
let loginService: LoginService;
let backend: MockBackend;
let injector: Injector;
let authenticationService: AuthenticationService;
beforeEach(() => {
injector = ReflectiveInjector.resolveAndCreate(<any> [
LoginService,
AuthenticationService,
BaseRequestOptions,
MockBackend,
provide(Http, {
useFactory: (mockBackend, defaultOptions) => new Http(mockBackend, defaultOptions),
deps: [MockBackend, BaseRequestOptions]
})
]);
loginService = <LoginService> injector.get(LoginService);
backend = <MockBackend> injector.get(MockBackend);
authenticationService = <AuthenticationService> injector.get(AuthenticationService)
});
afterEach(() => backend.verifyNoPendingRequests());
it('should authenticate with the web api', () => {
let loginUrl = Constants.loginUrl;
let loginData:LoginModel = new LoginModel('username', 'password');
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.url).toEqual(loginUrl);
c.mockRespond(new Response(new ResponseOptions({ body: '{"token": "mockAuth"}' })));
});
//Correct login data
loginService.postLogin(loginData).subscribe((data) => {
expect(data.token).toBe('mockAuth');
});
});
Also, how do you guys debug when running tests? console.log doesn't seem to work and neither does debugger;

Alright, so it seems the issue was calling the method in the .map section instead of executing everything directly there.
Solution:
delete authsuccess
.map((response) => {
this.isAuthenticated = true;
this.authenticationService.saveToken(response.json().token);
return response.json();
})

Related

How to call a mock service inside a beforeeach in Angular 5 jasmine/karma test case

I am facing an issue when calling mock service inside beforeEach function.
To make get the access token from the login so i need to run this mock service for all the function in the spec file so that we can make it run this in an orderly manner.
beforeEach(inject([LoginService, MockBackend], (Service: LoginService, mockBackend: MockBackend) => {
loginService = Service;
backend = mockBackend;
it('#login should call endpoint and return it\'s result', (done) => {
backend.connections.subscribe((connection: MockConnection) => {
const options = new ResponseOptions({
body: JSON.stringify({ success: true })
});
connection.mockRespond(new Response(options));
// Check the request headers
expect(connection.request.headers.get('Content-Type')).toEqual('application/json');
});
loginService.login('new', 'secret')
.subscribe((response) => {
sessionStorage.setItem('token', JSON.stringify(response.token));
done();
fixture = TestBed.createComponent(DashboardComponent);
router.initialNavigation();
component = fixture.componentInstance;
fixture.detectChanges();
},
(error) => {
expect(error).toThrowError();
});
});
}));

Http request in ionic 2

I am new to Ionic framework & going to develop an app which utilises rest api from my server. In each API I have to pass a token in http header. If this token is valid then I return response & inflate the listview. If token is not valid then I have to hit another API to generate token & again hit first API to fetch data.
My problem is second case. When token is invalid, it is generate successfully & then calling first API is also successful but this time listview is not inflated.
Please help.
home.ts
loadPeople(){
this.dataService.load()
.then(data => {
this.mylist = data;
});
}
data-provider.ts
load() {
if (this.data) {
return Promise.resolve(this.data);
}
return new Promise(resolve => {
let headers = new Headers({ 'token': this.token });
let options = new RequestOptions({ headers: headers });
this.http.get('myurl1', options)
.map(res => res.json())
.subscribe(data => {
if(data.message === 'TOKEN_NOTVALID'){
this.generateToken();
}else{
this.data = data.result;
resolve(this.data);
}
});
});
}
generateAccessToken(){
var creds = "param1=xxx&param2=zzz";
var headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
this.http.post('myUrlHere', creds, {
headers: headers
})
.map(res => res.json())
.subscribe(
data => {
this.token = data.token;
this.load();
});
}
The problem is that when token is invalid , the Promise is never resolved. Only the second call to load function Promise is resolved. So you need to resolve the Promise when token is not valid. Just because the name of the function is resolve in the Promise, doesnt mean that the second call to load function will resolve the first call to load function.
You can return the data promise in generateAccessToken then resolve the promise with data returned.
load() {
if (this.data) {
return Promise.resolve(this.data);
}
return new Promise(resolve => {
let headers = new Headers({ 'token': this.token });
let options = new RequestOptions({ headers: headers });
this.http.get('myurl1', options)
.map(res => res.json())
.subscribe(data => {
if(data.message === 'TOKEN_NOTVALID'){
this.generateToken().then(data => { resole(data) } );
}else{
this.data = data.result;
resolve(this.data);
}
});
});
}
generateAccessToken(){
var creds = "param1=xxx&param2=zzz";
var headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
return this.http.post('myUrlHere', creds, {
headers: headers
})
.map(res => res.json())
.toPromise()
.then(
data => {
this.token = data.token;
return this.load();
});
}
You will have to import toPromise
import 'rxjs/add/operator/toPromise';

Unit test and Assert http.get queryString call in Angular2

I have a DataService and I want to assert that the year is getting set in the query string correctly. Is there a way to spyOn the http.get call or to access it? I don't know the correct approach to testing this. I'm using Angular 2.2.0.
The DataService
constructor(private http: Http) { }
public getEnergyData(option: string): Promise<EnergyDataDto[]> {
return this.http.get(this.getEnergyDataApiUrl(option)).toPromise().then((response) => {
this.energyDataCache = this.parseEnergyDataResponse(response);
return this.energyDataCache;
}).catch(this.handleError);
}
protected getEnergyDataApiUrl(option: string) {
return `/api/solar?year=${option}`;
}
protected parseEnergyDataResponse(response: Response) {
return response.json().data;
}
dataservice.spec.ts
describe('Given the DataService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [DataService, { provide: XHRBackend, useClass: MockBackend }],
});
});
describe('When getting the energy data', () => {
let backend: MockBackend;
let service: EnergyDataService;
let fakeEnergyData: EnergyDataDto[];
let response: Response;
const makeEnergyData = () => {
let data = [];
let one = new EnergyDataDto();
one.year = 2007;
one.countryName = 'Denmark';
one.quantity = '100000';
data.push(one);
return data;
};
beforeEach(inject([Http, XHRBackend], (http: Http, be: MockBackend) => {
backend = be;
service = new EnergyDataService(http);
fakeEnergyData = makeEnergyData();
let options = new ResponseOptions({ status: 200, body: { data: fakeEnergyData } });
response = new Response(options);
}));
it('should return fake values', async(inject([], () => {
backend.connections.subscribe((c: MockConnection) => c.mockRespond(response));
service.getEnergyData('all').then(data => {
expect(data.length).toBe(1);
expect(data[0].countryName).toBe('Denmark');
});
})));
it('should use year in query string', async(inject([], () => {
spyOn(service, 'getEnergyDataApiUrl').and.callThrough();
backend.connections.subscribe((c: MockConnection) => c.mockRespond(response));
service.getEnergyData('2007').then(data => {
// I was hoping to use backendend somehow instead, but it's not in scope when I debug it.
expect((<any>service).getEnergyDataApiUrl).toHaveBeenCalledWith('/api/solar?year=2007');
});
})));
You should do this in the mockBackend.connections subscription. This is when you have access to the URL from the MockConnection
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.url).toBe(...)
c.mockRespond(response)
});

Angular 2 Observable testing Error: Cannot use setInterval from within an async zone test

I'm trying to test a component, which uses a service that makes async http calls. The service returns an Observable, which the component subscribes on.
Service code snippet:
getRecentMachineTemperatures(_machine_Id): Observable<IDeviceReadings[]> {
return this.http.get(TemperatureService.URL + _machine_Id)
.map(response => { return response.json(); })
.map((records: Array<any>) => {
let result = new Array<IDeviceReadings>();
if (records) {
records.forEach((record) => {
let device = new IDeviceReadings();
device.device_id = record.device_id;
if (record.d) {
record.d.forEach((t) => {
let temperature = new ITemperature();
temperature.timestamp = t.timestamp;
temperature.value = t.temperature;
device.temperatures.push(temperature);
});
}
result.push(device);
});
}
return result;
});
}
Component code snippet:
ngOnInit() {
this.getRecentTemperatures();
}
getRecentTemperatures() {
this.temperatureService.getRecentMachineTemperatures(this.machine_id)
.subscribe(
res => {
let device1 = res[0];
this.deviceId = device1.device_id;
this.initTemperatures(device1.temperatures);
this.updateChart();
},
error => console.log(error));
}
My Test sets up dependencies, spies on the service 'getRecentMachineTemperatures' and sets i to return some stub data. I've been googling around for ways to test this, thus resulting in 3 different test, trying to test the same thing. Each giving me a different error.
temperature.component.spec.ts:
let machine_id = 1;
let comp: TemperatureComponent;
let fixture: ComponentFixture<TemperatureComponent>;
let de: DebugElement;
let el: HTMLElement;
let temperatureService: TemperatureService;
let stubDevices: IDeviceReadings[];
let stubTemperatures: ITemperature[];
let spyRecentTemps: Function;
describe('Component: Temperature', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TemperatureComponent],
imports: [ ChartsModule ],
providers: [
MockBackend,
BaseRequestOptions,
{ provide: Http,
useFactory: (backend, defaultOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]},
TemperatureService
]
});
stubDevices = new Array<IDeviceReadings>();
let stubDevice = new IDeviceReadings();
stubDevice.device_id = 'stub device';
stubDevice.temperatures = new Array<ITemperature>();
let stubTemp = new ITemperature();
stubTemp.timestamp = new Date().getTime();
stubTemp.value = 10;
stubDevice.temperatures.push(stubTemp);
stubDevices.push(stubDevice);
stubTemperatures = new Array<ITemperature>();
let stubTemp2 = new ITemperature();
stubTemp.timestamp = new Date().getTime() + 1;
stubTemp.value = 11;
stubTemperatures.push(stubTemp2);
fixture = TestBed.createComponent(TemperatureComponent);
comp = fixture.componentInstance;
temperatureService = fixture.debugElement.injector.get(TemperatureService);
spyRecentTemps = spyOn(temperatureService, 'getRecentMachineTemperatures')
.and.returnValue(Observable.of(stubDevices).delay(1));
// get the "temperature-component" element by CSS selector (e.g., by class name)
de = fixture.debugElement.query(By.css('.temperature-component'));
el = de.nativeElement;
});
it('should show device readings after getRecentTemperatures subscribe (fakeAsync)', fakeAsync(() => {
fixture.detectChanges();
expect(spyRecentTemps.calls.any()).toBe(true, 'getRecentTemperatures called');
tick(1000);
fixture.detectChanges();
expect(el.textContent).toContain(stubDevices[0].temperatures[0].timestamp);
expect(el.textContent).toContain(stubDevices[0].temperatures[0].value);
}));
it('should show device readings after getRecentTemperatures subscribe (async)', async(() => {
fixture.detectChanges();
expect(spyRecentTemps.calls.any()).toBe(true, 'getRecentTemperatures called');
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(el.textContent).toContain(stubDevices[0].temperatures[0].timestamp);
expect(el.textContent).toContain(stubDevices[0].temperatures[0].value);
});
}));
it('should show device readings after getRecentTemperatures subscribe (async) (done)', (done) => {
async(() => {
fixture.detectChanges();
expect(spyRecentTemps.calls.any()).toBe(true, 'getRecentTemperatures called');
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(el.textContent).toContain(stubDevices[0].temperatures[0].timestamp);
expect(el.textContent).toContain(stubDevices[0].temperatures[0].value);
}).then(done);
});
});
});
fakeAsync fails with: 'Error: 1 timer(s) still in the queue.'
async fails with: 'Error: Cannot use setInterval from within an async zone test.'
async (done) fails with: 'Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'
How would I go about testing components with a async service dependency?
From what I understand it might be something about the AsyncScheduler within the Rx library using Date().now instead of faked time (https://github.com/angular/angular/issues/10127). If so has this been fixed? Or anyone found a workaround?
I'm using angular-cli: 1.0.0-beta.16. node: 4.4.2. npm: 3.10.6. webpack 2.1.0-beta.22.
I had ..
import 'rxjs/add/operator/timeout';
return this.http[method](url, emit, this.options)
.timeout(Config.http.timeout, new Error('timeout'))
Which was causing this error. I believe under the hood RXJS .timeout is calling setInterval.
I fixed this by switching ...
it('blah', async(() => {
to
it('blah', (done) => {

angular2 - how to simulate error on http.post unit test

I m writing a Uni-test for a login Method with an HTTP.post call, like:
this.http.post( endpoint, creds, { headers: headers})
.map(res => res.json())
.subscribe(
data => this.onLoginComplete(data.access_token, credentials),
err => this.onHttpLoginFailed(err),
() => this.trace.debug(this.componentName, "Login completed.")
);
The problem is that i'm not able to simulate the error branch; everytime is called the onLoginComplete Method;
here is my test:
it("check that Atfer Login, console show an error ", inject(
[TraceService, Http, MockBackend, WsiEndpointService],
(traceService: TraceService, http: Http,
backend: MockBackend, wsiEndpoint: WsiEndpointService) => {
let tokenTest: number = 404 ;
let response: ResponseOptions = null {} // i think i have to modify this
let connection: any;
backend.connections.subscribe((c: any) => connection = c);
let authService: AuthService = new AuthService(http, Service1, Service2);
authenticationservice.login({ "username": "a", "password": "1" });
connection.mockRespond(new Response(response));
expect(ERROR);
}));
Thanks again to everyone.
First you need to override the XHRBackend class by the MockBackend one:
describe('HttpService Tests', () => {
beforeEachProviders(() => {
return [
HTTP_PROVIDERS,
provide(XHRBackend, { useClass: MockBackend }),
HttpService
];
});
(...)
});
Notice that HttpService is the service that uses the Http object and I want to test.
Then you need to inject the mockBackend and subscribe on its connections property. When a request is sent, the corresponding callback is called and you can specify the response elements like the body. The service will receive this response as the response of the call. So you'll be able to test your service method based on this.
Below I describe how to test the getItems method of the HttpService:
it('Should return a list of items', inject([XHRBackend, HttpService, Injector], (mockBackend, httpService, injector) => {
mockBackend.connections.subscribe(
(connection: MockConnection) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: [ { id: '1', label: 'item1' }]
})));
});
httpService.getItems().subscribe(
items => {
expect(items).toEqual([ { id: '1', label: 'item1' }]);
});
});
});
Here is the code of getItems method of the HttpService:
#Injectable()
export class HttpService {
constructor(private http:Http) {
}
getItems(): Observable<any[]> {
return this.http.get('/items').map(res => res.json());
}
}
To simulate an error simply use the mockError method instead of the mockResponseone:
mockBackend.connections.subscribe(
(connection: MockConnection) => {
connection.mockError(new Error('some error'));
});
You can simulate an error like this:
connection.mockError(new Response(new ResponseOptions({
body: '',
status: 404,
})));
I created a small class
import {ResponseOptions, Response} from '#angular/http';
export class MockError extends Response implements Error {
name: any;
message: any;
constructor(status: number, body: string = '') {
super(new ResponseOptions({status, body}));
}
}
which can use like this
connection.mockError(new MockError(404));