import {Page, Platform} from 'ionic-angular';
import { Http, Headers, Response, RequestOptions, HTTP_PROVIDERS} from 'angular2/http';
import { bootstrap } from 'angular2/platform/browser';
import { Component, Injectable, Inject } from 'angular2/core';
import 'rxjs/Rx';
import { CORE_DIRECTIVES, FORM_DIRECTIVES } from 'angular2/common';
import {Observable} from 'rxjs/Observable';
#Page({
directives: [ CORE_DIRECTIVES, FORM_DIRECTIVES ],
templateUrl: 'build/pages/getting-started/getting-started.html'
})
#Injectable()
export class GettingStartedPage {
public platform;
public networkState;
private headers;
private _apiUrl = 'http://172.16.2.115:3004/message';
subject: string;
message: string;
comments: string;
constructor(platform: Platform, public http: Http) {
this.platform = platform;
this.headers = new Headers();
this.headers.append('Content-Type', 'application/x-www-form-urlencoded');
}
onSubmit(value) {
this.send(value.subject, value.body);
}
send(subject, body)
{
var message = "subject=" + subject + "&body=" + body;
let result = this.http.post(this._apiUrl,
body,
{
headers: this.headers
}).map(res => {
this.comments = res.json();
});
this.send(subject, body).subscribe(res => {
console.log(message);
console.log(this._apiUrl);
});
return result;
}
}
I am trying to create a mobile app using Ionic2 and Angular2 beta.This app will send an email using POST to a Rails web service. The Rails web service is working just fine. I dont seem to be getting to work the mobile app.
There was a misunderstanding
this.send(subject, body).subscribe(res => {
console.log(message);
console.log(this._apiUrl);
});
would be in a different method
onSubmit(value) {
this.send(value.subject, value.body)
.subscribe(res => {
console.log(message);
console.log(this._apiUrl);
});
}
send(subject, body)
{
var message = "subject=" + subject + "&body=" + body;
return this.http.post(this._apiUrl,
body,
{
headers: this.headers
}).map(res => {
this.comments = res.json();
});
}
You should refactor your code this way:
onSubmit(value) {
this.send(value.subject, value.body).subscribe(res => {
console.log(message);
console.log(this._apiUrl);
});
}
send(subject, body)
{
var message = "subject=" + subject + "&body=" + body;
let result = this.http.post(this._apiUrl,
body,
{
headers: this.headers
}).map(res => {
this.comments = res.json();
});
return result;
}
Related
I want to write a unit test for my payment service but I'm receiving this error:
source.subscribe is not a function
at ./node_modules/rxjs/src/internal/lastValueFrom.ts:60:12
This is my service
import { HttpService } from '#nestjs/axios';
import { Injectable } from '#nestjs/common';
import { lastValueFrom } from 'rxjs';
import { PaymentInfo } from 'src/utils/types/paymentInfo';
#Injectable()
export class PaymentsService {
constructor(private readonly httpService: HttpService) {}
private createHeaderWithAuth(auth, contentType = 'application/json') {
return {
headers: {
authorization: auth.replace('Bearer', '').trim(),
'Content-Type': contentType,
},
};
}
async makePayment(auth: string, paymentInfo: PaymentInfo) {
const configs = this.createHeaderWithAuth(auth);
const response = await lastValueFrom(
await this.httpService.post(
`${process.env.PAYMENT_URL}/transaction/pay`,
paymentInfo,
configs
)
).catch((error) => {
console.log(error);
throw new Error(error.response.data.message);
});
return response.data;
}
}
So with a bit of searching and tinkering found out that this is caused by my import of a rxjs function to resolve the observable setted by axios.
I've searched ways to mock this function so I can properly test my service. But none of them gave me a solution, the questions i found only revolved around functions with modules, but these have none since is imported from a third party lib.
This is my test suite:
describe('Payments Service', () => {
let service: PaymentsService;
let mockedHttpService = {
post: jest
.fn()
.mockImplementation(
async (
url: string,
paymentInfo: PaymentInfo,
header = mockedHeader
) => {
return { mockedSuccessfulResponse };
}
),
get: jest
.fn()
.mockImplementation(async (url: string, header = mockedHeader) => {
return { ...mockedSuccessfulResponse, data: mockedUserCards };
}),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PaymentsService,
{
provide: HttpService,
useValue: mockedHttpService,
},
],
}).compile();
service = module.get<PaymentsService>(PaymentsService);
});
describe('Initialize', () => {
it('should define service', () => {
expect(service).toBeDefined();
});
describe('makePayment', () => {
it('should make a payment', async () => {
const payment = await service.makePayment(mockedAuth, mockedPaymentInfo);
expect(mockedHttpService.post).toHaveBeenCalledWith(
`${process.env.PAYMENT_URL}/transaction/pay`,
mockedPaymentInfo,
mockedHeader
);
expect(payment).toBe(mockedSuccessfulResponse);
});
});
});
Ps.: I removed the mocked objects to reduce the amount of code to read
you should use the of operator from rxjs, and drop the async keyword. Like:
.mockImplementation(
(
url: string,
paymentInfo: PaymentInfo,
header = mockedHeader
) => {
return of({ mockedSuccessfulResponse });
}
otherwise lastValueFrom won't receive an observable object.
I have created a Django and Angular application to upload files. It was working without errors until I integrated a login page. I have not been able to upload files since integration. I get 401 - "Unauthorized" error. What could have possibly gone wrong?
Auth-interceptor:
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest,HttpErrorResponse } from "#angular/common/http";
import { Injectable } from "#angular/core";
import { catchError, Observable, throwError } from "rxjs";
import { LoginService } from "src/services/login.service";
#Injectable()
export class AuthInterceptorService implements HttpInterceptor {
constructor(private authService: LoginService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.authService.isLoggedIn()) {
const token = this.authService.getAuthToken();
console.log("intercept",token)
// If we have a token, we set it to the header
request = request.clone({
setHeaders: {Authorization: `Token ${token}`}
});
}
return next.handle(request)
}
}
fileupload.component.ts:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup } from '#angular/forms';
import { LoginService } from 'src/services/login.service';
import { FileUploader, FileLikeObject } from 'ng2-file-upload';
import { concat, Observable } from 'rxjs';
import { HttpEvent, HttpEventType } from '#angular/common/http';
#Component({
selector: 'app-fileupload',
templateUrl: './fileupload.component.html',
styleUrls: ['./fileupload.component.scss']
})
export class FileuploadComponent {
DJANGO_SERVER = 'http://127.0.0.1:8081'
public uploader: FileUploader = new FileUploader({});
public hasBaseDropZoneOver: boolean = false;
constructor(private formBuilder: FormBuilder, private uploadService: LoginService) { }
fileOverBase(event): void {
this.hasBaseDropZoneOver = event;
}
getFiles(): FileLikeObject[] {
return this.uploader.queue.map((fileItem) => {
return fileItem.file;
});
}
upload() {
let files = this.getFiles();
console.log(files);
let requests= [];
files.forEach((file) => {
let formData = new FormData();
formData.append('file' , file.rawFile, file.name);
requests.push(this.uploadService.upload(formData));
console.log(requests,file)
});
concat(...requests).subscribe(
(res) => {
console.log(res);
},
}
);
}}
console.log(err);
}
);
}}
service:
public upload(formData) {
let token= localStorage.getItem('token');
return this.http.post<any>(`${this.DJANGO_SERVER}/upload/`, formData).pipe(map((res) => {
console.log(res)
})
)
}
Thank you
I resolved the issue. It was because I was usign interceptor and I was using third party API for authentication. So instead of Django token, the third party APIs token was sent in header of POST request.
How I resolved it?
I used Httpbackend to process POST requests to Django DB so that the request is not intercepted and then I added custom header (with Django token to the reuest). I used the code snippet on this website: https://levelup.gitconnected.com/the-correct-way-to-make-api-requests-in-an-angular-application-22a079fe8413
Stuck on how to test a component that has alot of third party dependencies. Pretty sure I'm approaching it the wrong way, I'm not sure where to start on this. Here's the component:
import { Component, OnInit } from '#angular/core';
import { Http, Response } from '#angular/http';
import { GameService } from '../game.service';
import { environment } from '../../../environments/environment';
import { ActivatedRoute } from '#angular/router';
import { SlimLoadingBarService } from 'ng2-slim-loading-bar';
#Component({
selector: 'app-list',
templateUrl: 'list.component.html',
styleUrls: ['list.component.scss']
})
export class ListComponent implements OnInit {
config = environment.config;
games;
headers;
params = {
searchTerm: '',
searchOwner: '',
searchType: '',
order: 'name',
orderType: 'DESC',
currentPage: 1,
page: 1
};
pages;
totalRecords;
recordsPerPage = 25;
exportUrl;
personasPage = 1;
searchUrl = environment.config.apiUrl + 'personas/';
public buffer: any;
public personas: any;
buttonSpinner: string = 'none';
constructor(
private gameService: GameService,
private http: Http,
private route: ActivatedRoute,
private loadingBar: SlimLoadingBarService
) { }
ngOnInit() {
this.getListing();
this.getPersonas();
}
sort(e, order) {
e.preventDefault();
this.params.orderType = (order === this.params.order && this.params.orderType === 'DESC') ? 'ASC' : 'DESC';
this.params.order = order;
this.params.page = this.params.currentPage = 1;
this.getListing();
}
updateListing(page) {
this.params.page = this.params.currentPage = page;
this.getListing();
}
getListing() {
this.loadingBar.start();
this.gameService.getGames(this.params)
.subscribe((res) => {
this.loadingBar.complete();
this.buttonSpinner = 'none';
this.games = res.json();
this.headers = res.headers;
this.totalRecords = this.headers.get('x-total');
this.recordsPerPage = this.headers.get('x-limit');
this.exportUrl = this.gameService.getExportLink(this.params);
});
}
getPersonas() {
this.http.get(this.searchUrl + '?page=' + this.personasPage)
.map((res: Response) => res.json())
.subscribe((d: {}) => {
this.buffer = d;
if (this.personas === undefined) {
this.personas = this.buffer;
// console.log(this.personas);
this.personasPage += 1;
this.getPersonas();
} else {
for (let key in this.buffer) {
this.personas.push(this.buffer[key]);
}
this.personasPage += 1;
if (this.buffer.length === 25) {
this.getPersonas();
}
}
});
}
onSubmitSearch() {
this.buttonSpinner = 'searching';
this.params.page = this.params.currentPage = 1;
this.getListing();
}
clearSearch(e) {
e.preventDefault();
this.buttonSpinner = 'clearing';
this.params.searchTerm = '';
this.params.searchType = '';
this.params.searchOwner = '';
this.params.page = this.params.currentPage = 1;
this.getListing();
}
}
Here's the Test I've tried writing:
import { TestBed, async, ComponentFixture } from '#angular/core/testing';
import {} from 'jasmine';
import { Http } from '#angular/http';
import { ActivatedRoute, RouterModule } from '#angular/router';
import { CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { ListComponent } from './list.component';
import { GameService } from '../game.service';
import { PageitComponent } from '../../shared/pageit/pageit.component';
import { SlimLoadingBarService } from 'ng2-slim-loading-bar';
import { LaddaModule } from 'angular2-ladda';
describe('ListComponent', () => {
let gameService, http, route, loadingBar;
let component: ListComponent;
let fixture: ComponentFixture<ListComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ ListComponent, PageitComponent ],
providers: [
{ provide: GameService, useValue: gameService }, { provide: Http, useValue: http }, { provide: ActivatedRoute, useValue: route },
{ provide: SlimLoadingBarService, useValue: loadingBar }
],
imports: [FormsModule, LaddaModule, RouterModule]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ListComponent);
component = fixture.componentInstance;
});
it('should create an instance of the component', () => {
component.ngOnInit();
expect(component).toBeDefined();
});
});
So far, I've got it to recognize the init, now it's breaking because of this.loadingBar.start(); inside the getListing function.
Just mock all the services and test the behavior of the component by checking that it makes the correct service class, by using expect(mockService.someMethod).toHaveBeenCalled().
For example
let loadingBar: SlimLoadingBarService;
let gameService: GameService;
beforeEach(() => {
loadingBar = {
start: jasmine.createSpy('start'),
complete: jasmine.createSpy('complete')
};
gameService = {
getGames: (params) => Observable.of(whatever)
};
TestBed.configureTestingModule({
providers: [
{ provide: SlimLoadingBarService, useValue: loadingBar },
{ provide: GameService, useValue: gameService }
]
})
})
it('..', () -> {
component.getListing();
expect(loadingBar.start).toHaveBeenCalled();
// component calls gameService.getGames is called, wait for async
// to finish by called fixture.whenStable
fixture.whenStable().then(() => {
expect(loadingBar.complete).toHaveBeedCalled();
})
})
For you Http call in the component, I would abstract that into a service so that you can mock that service.
I am trying to write unit tests for my API service but have some trouble catching HTTP errors. I am following this guide along with the Angular2 docs since the guide is (slightly) out of date in some minor areas.
All unit tests pass apart from those where an error is thrown by the service (due to error HTTP status code). I can tell this by logging out response.ok. From what i've read this has something to do with the unit tests not executing asynchronously, hence, not waiting for the error response. However, I have no idea why this is the case here since I have used the async() utility function in the beforeEach method.
API Service
get(endpoint: string, authenticated: boolean = false): Observable<any> {
endpoint = this.formatEndpoint(endpoint);
return this.getHttp(authenticated) // Returns #angular/http or a wrapper for handling auth headers
.get(endpoint)
.map(res => this.extractData(res))
.catch(err => this.handleError(err)); // Not in guide but should work as per docs
}
private extractData(res: Response): any {
let body: any = res.json();
return body || { };
}
private handleError(error: Response | any): Observable<any> {
// TODO: Use a remote logging infrastructure
// TODO: User error notifications
let errMsg: string;
if (error instanceof Response) {
const body: any = error.json() || '';
const err: string = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''}${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
Error unit test
// Imports
describe('Service: APIService', () => {
let backend: MockBackend;
let service: APIService;
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
BaseRequestOptions,
MockBackend,
APIService,
{
deps: [
MockBackend,
BaseRequestOptions
],
provide: Http,
useFactory: (backend: XHRBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
}
},
{provide: AuthHttp,
useFactory: (http: Http, options: BaseRequestOptions) => {
return new AuthHttp(new AuthConfig({}), http, options);
},
deps: [Http, BaseRequestOptions]
}
]
});
const testbed: any = getTestBed();
backend = testbed.get(MockBackend);
service = testbed.get(APIService);
}));
/**
* Utility function to setup the mock connection with the required options
* #param backend
* #param options
*/
function setupConnections(backend: MockBackend, options: any): any {
backend.connections.subscribe((connection: MockConnection) => {
const responseOptions: any = new ResponseOptions(options);
const response: any = new Response(responseOptions);
console.log(response.ok); // Will return false during the error unit test and true in others (if spyOn log is commented).
connection.mockRespond(response);
});
}
it('should log an error to the console on error', () => {
setupConnections(backend, {
body: { error: `Some strange error` },
status: 400
});
spyOn(console, 'error');
spyOn(console, 'log');
service.get('/bad').subscribe(null, e => {
// None of this code block is executed.
expect(console.error).toHaveBeenCalledWith("400 - Some strange error");
console.log("Make sure an error has been thrown");
});
expect(console.log).toHaveBeenCalledWith("Make sure an error has been thrown."); // Fails
});
Update 1
when I check the first callback, response.ok is undefined. This leads me to believe that there is something wrong in the setupConnections utility.
it('should log an error to the console on error', async(() => {
setupConnections(backend, {
body: { error: `Some strange error` },
status: 400
});
spyOn(console, 'error');
//spyOn(console, 'log');
service.get('/bad').subscribe(res => {
console.log(res); // Object{error: 'Some strange error'}
console.log(res.ok); // undefined
}, e => {
expect(console.error).toHaveBeenCalledWith("400 - Some strange error");
console.log("Make sure an error has been thrown");
});
expect(console.log).toHaveBeenCalledWith("Make sure an error has been thrown.");
}));
Update 2
If, instead of catching errors in the get method I do it explicitly in map then still have same problem.
get(endpoint: string, authenticated: boolean = false): Observable<any> {
endpoint = this.formatEndpoint(endpoint);
return this.getHttp(authenticated).get(endpoint)
.map(res => {
if (res.ok) return this.extractData(res);
return this.handleError(res);
})
.catch(this.handleError);
}
Update 3
After some discussion this issue submitted
Here is my working solution which is similar to above suggestions but with more clarity:
it('should log an error to the console on error', async(inject([AjaxService, MockBackend], (
ajaxService: AjaxService, mockBackend: MockBackend) => {
service = ajaxService;
backend = mockBackend;
backend.connections.subscribe((connection: MockConnection) => {
const options: any = new ResponseOptions({
body: { error: 'Some strange error' },
status: 404
});
const response: any = new Response(options);
connection.mockError(response);
});
spyOn(console, 'error');
service.get('/bad').subscribe(res => {
console.log(res); // Object{error: 'Some strange error'}
}, e => {
expect(console.error).toHaveBeenCalledWith('404 - Some strange error');
});
})));
Reference full working code:
Below are all possible test scenarios.
Note: Don't worry about AjaxService. It's my custom wrapper on angular http service which is being used as a interceptor.
ajax.service.spec.ts
import { AjaxService } from 'app/shared/ajax.service';
import { TestBed, inject, async } from '#angular/core/testing';
import { Http, BaseRequestOptions, ResponseOptions, Response } from '#angular/http';
import { MockBackend, MockConnection } from '#angular/http/testing';
describe('AjaxService', () => {
let service: AjaxService = null;
let backend: MockBackend = null;
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backendInstance: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backendInstance, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
},
AjaxService
]
});
}));
it('should return mocked post data',
async(inject([AjaxService, MockBackend], (
ajaxService: AjaxService, mockBackend: MockBackend) => {
service = ajaxService;
backend = mockBackend;
backend.connections.subscribe((connection: MockConnection) => {
const options = new ResponseOptions({
body: JSON.stringify({ data: 1 }),
});
connection.mockRespond(new Response(options));
});
const reqOptions = new BaseRequestOptions();
reqOptions.headers.append('Content-Type', 'application/json');
service.post('', '', reqOptions)
.subscribe(r => {
const out: any = r;
expect(out).toBe(1);
});
})));
it('should log an error to the console on error', async(inject([AjaxService, MockBackend], (
ajaxService: AjaxService, mockBackend: MockBackend) => {
service = ajaxService;
backend = mockBackend;
backend.connections.subscribe((connection: MockConnection) => {
const options: any = new ResponseOptions({
body: { error: 'Some strange error' },
status: 404
});
const response: any = new Response(options);
connection.mockError(response);
});
spyOn(console, 'error');
service.get('/bad').subscribe(res => {
console.log(res); // Object{error: 'Some strange error'}
}, e => {
expect(console.error).toHaveBeenCalledWith('404 - Some strange error');
});
})));
it('should extract mocked data with null response',
async(inject([AjaxService, MockBackend], (
ajaxService: AjaxService, mockBackend: MockBackend) => {
service = ajaxService;
backend = mockBackend;
backend.connections.subscribe((connection: MockConnection) => {
const options = new ResponseOptions({
});
connection.mockRespond(new Response(options));
});
const reqOptions = new BaseRequestOptions();
reqOptions.headers.append('Content-Type', 'application/json');
service.get('test', reqOptions)
.subscribe(r => {
const out: any = r;
expect(out).toBeNull('extractData method failed');
});
})));
it('should log an error to the console with empty response', async(inject([AjaxService, MockBackend], (
ajaxService: AjaxService, mockBackend: MockBackend) => {
service = ajaxService;
backend = mockBackend;
backend.connections.subscribe((connection: MockConnection) => {
const options: any = new ResponseOptions({
body: {},
status: 404
});
const response: any = new Response(options);
connection.mockError(response);
});
spyOn(console, 'error');
service.get('/bad').subscribe(res => {
console.log(res); // Object{error: 'Some strange error'}
}, e => {
expect(console.error).toHaveBeenCalledWith('404 - {}');
});
// handle null response in error
backend.connections.subscribe((connection: MockConnection) => {
connection.mockError();
});
const res: any = null;
service.get('/bad').subscribe(res, e => {
console.log(res);
}, () => {
expect(console.error).toHaveBeenCalledWith(null, 'handleError method with null error response got failed');
});
})));
});
ajax.service.ts
import { Injectable } from '#angular/core';
import { Http, Response, RequestOptionsArgs, BaseRequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
/**
* Wrapper around http, use this for all http operations.
* It has centralized error handling as well.
* #export
* #class AjaxService
*/
#Injectable()
export class AjaxService {
/**
* Creates an instance of AjaxService.
* #param {Http} http
*
* #memberOf AjaxService
*/
constructor(
private http: Http,
) { }
/**
* Performs a request with get http method.
*
* #param {string} url
* #param {RequestOptionsArgs} [options]
* #returns {Observable<Response>}
*
* #memberOf AjaxService
*/
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
options = this.getBaseRequestOptions(options);
options = this.setHeaders(options);
return this.http.get(url, options)
.map(this.extractData)
.catch(this.handleError);
}
/**
* Performs a request with post http method.
*
* #param {string} url
* #param {*} body
* #param {RequestOptionsArgs} [options]
* #returns {Observable<Response>}
*
* #memberOf AjaxService
*/
post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
options = this.getBaseRequestOptions(options);
options = this.setHeaders(options);
return this.http.post(url, body, options)
.map(this.extractData)
.catch(this.handleError);
}
/**
* Util function to fetch data from ajax response
*
* #param {Response} res
* #returns
*
* #memberOf AjaxService
*/
private extractData(res: Response) {
const body = res.json();
const out = body && body.hasOwnProperty('data') ? body.data : body;
return out;
}
/**
* Error handler
* Future Scope: Put into remote logging infra like into GCP stackdriver logger
* #param {(Response | any)} error
* #returns
*
* #memberOf AjaxService
*/
private handleError(error: Response | any) {
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''}${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
/**
* Init for RequestOptionsArgs
*
* #private
* #param {RequestOptionsArgs} [options]
* #returns
*
* #memberOf AjaxService
*/
private getBaseRequestOptions(options: RequestOptionsArgs = new BaseRequestOptions()) {
return options;
}
/**
* Set the default header
*
* #private
* #param {RequestOptionsArgs} options
* #returns
*
* #memberOf AjaxService
*/
private setHeaders(options: RequestOptionsArgs) {
if (!options.headers || !options.headers.has('Content-Type')) {
options.headers.append('Content-Type', 'application/json');
}
return options;
}
}
From what i've read this has something to do with the unit tests not executing asynchronously, hence, not waiting for the error response. However, I have no idea why this is the case here since I have used the async() utility function in the beforeEach method
You need to use it in the test case (the it). What async does is create an test zone that waits for all async tasks to complete before completing the test (or test area, e.g. beforeEach).
So the async in the beforeEach is only waiting for the async tasks to complete in the method before exiting it. But the it also needs that same thing.
it('should log an error to the console on error', async(() => {
}))
UPDATE
Aside from the missing async, there seems to be a bug with the MockConnection. If you look at the mockRespond, it always calls next, not taking into consideration the status code
mockRespond(res: Response) {
if (this.readyState === ReadyState.Done || this.readyState === ReadyState.Cancelled) {
throw new Error('Connection has already been resolved');
}
this.readyState = ReadyState.Done;
this.response.next(res);
this.response.complete();
}
They have a mockError(Error) method, which is what calls error
mockError(err?: Error) {
// Matches ResourceLoader semantics
this.readyState = ReadyState.Done;
this.response.error(err);
}
but this does not call allow you to pass a Response. This is inconsistent with how the real XHRConnection works, which checks for the status, and sends the Response either through the next or error, but is the same Response
response.ok = isSuccess(status);
if (response.ok) {
responseObserver.next(response);
// TODO(gdi2290): defer complete if array buffer until done
responseObserver.complete();
return;
}
responseObserver.error(response);
Sounds like a bug to me. Something you should probably report. They should allow you to either send the Response in the mockError or do the same check in the mockRespond that they do in the XHRConnection.
Updated (by OP) SetupConnections()
Current solution
function setupConnections(backend: MockBackend, options: any): any {
backend.connections.subscribe((connection: MockConnection) => {
const responseOptions: any = new ResponseOptions(options);
const response: any = new Response(responseOptions);
// Have to check the response status here and return the appropriate mock
// See issue: https://github.com/angular/angular/issues/13690
if (responseOptions.status >= 200 && responseOptions.status <= 299)
connection.mockRespond(response);
else
connection.mockError(response);
});
}
I am trying to write unit tests for my service which makes Http requests.
I have a service that returns a Http.get() request followed by a .map(). I am having trouble getting my mocked backend to return something that doesn't error on the .map(). The error I'm getting is:
this._http.get(...).map is not a function
I have been using this article as my main guide throughout.
If I remove the .map() from my service function, I don't get any errors. How can I get my mocked response to have a .map() function that I can call?
Note: I am currently using RC.4
Here is my service:
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { AppSettings } from '../../../settings';
import { Brand } from '../../models/index';
#Injectable()
export class BrandDataService {
allBrands : Brand[];
groups : any;
groupNames : string[];
constructor (
private _http : Http
) {}
/**
* Get all brands
*/
public getAllBrands () :Observable<any> {
let url = AppSettings.BRAND_API_URL + 'brands';
return this._http.get( url )
.map( this.createAndCacheBrands )
.catch( (error) => {
return Observable.throw( error );
});
}
private createAndCacheBrands (res:Response) {
...
}
}
And here is my spec file, which is using MockBackend and other associated libraries to mock the backend for these tests:
// vendor dependencies
import { Http, BaseRequestOptions, Response, ResponseOptions, RequestMethod } from '#angular/http';
import { addProviders, inject } from '#angular/core/testing';
import { MockBackend, MockConnection } from '#angular/http/testing';
// Service to test
import { BrandDataService } from './brandData.service';
describe( 'Brand data service', () => {
let service : BrandDataService = null;
let backend : MockBackend = null;
// Provide a mock backend implementation
beforeEach(() => {
addProviders([
MockBackend,
BaseRequestOptions,
{
provide : Http,
useFactory : (backendInstance : MockBackend, defaultOptions : BaseRequestOptions) => {
return new Http(backendInstance, defaultOptions);
},
deps : [MockBackend, BaseRequestOptions]
},
BrandDataService
])
})
beforeEach (inject([BrandDataService, MockBackend], (_service : BrandDataService, mockBackend : MockBackend) => {
service = _service;
backend = mockBackend;
}));
it ('should return all brands as an Observable<Response> when asked', (done) => {
// Set the mock backend to respond with the following options:
backend.connections.subscribe((connection : MockConnection) => {
// Make some expectations on the request
expect(connection.request.method).toEqual(RequestMethod.Get);
// Decide what to return
let options = new ResponseOptions({
body : JSON.stringify({
success : true
})
});
connection.mockRespond(new Response(options));
});
// Run the test.
service
.getAllBrands()
.subscribe(
(data) => {
expect(data).toBeDefined();
done();
}
)
});
});
You need to import rxjs so you can use map:
import 'rxjs/Rx';
Or, you can import only map operator so your app doesn't load files you won't use:
import 'rxjs/add/operator/map';