I'd like to unit test a service, however, when running the test, I get the following error:
Uncaught (in promise) SyntaxError: Unexpected token o in JSON at position 1 at MapSubscriber.project (auth.service.ts:217) at MapSubscriber.Array.concat.MapSubscriber._next (map.js:77) at MapSubscriber.Array.concat.Subscriber.next (Subscriber.js:89) at TakeSubscriber.Array.concat.TakeSubscriber._next (take.js:80) at TakeSubscriber.Array.concat.Subscriber.next (Subscriber.js:89) at ReplaySubject.Array.concat.ReplaySubject._subscribe (ReplaySubject.js:55) at ReplaySubject.Array.concat.Observable._trySubscribe (Observable.js:57) at ReplaySubject.Array.concat.Subject._trySubscribe (Subject.js:97) at ReplaySubject.Array.concat.Observable.subscribe (Observable.js:45) at TakeOperator.Array.concat.TakeOperator.call (take.js:60) at AnonymousSubject.Array.concat.Observable.subscribe (Observable.js:42) at MapOperator.Array.concat.MapOperator.call (map.js:54) at AnonymousSubject.Array.concat.Observable.subscribe (Observable.js:42) at CatchOperator.Array.concat.CatchOperator.call (catch.js:79) at AnonymousSubject.Array.concat.Observable.subscribe (Observable.js:42)
The corresponding line (auth.service.ts:217
) is highlighted below in code. Running the application works perfectly fine, therefore I do not see an obvious reason for the test to fail.
NB: This SO post suggests that I'm parsing the object twice. But shouldn't it fail when running the application then, too?
auth.service.ts
public login(username: string, password: string): Observable<User> {
// ...
return this.http.request(path, requestOptions).map((response: Response) => {
if (response.status === 200) {
const token = response.json().token; // <<-- Uncaught (in promise) SyntaxError: Unexpected token o in JSON at position 1
const user = this.extractUser(response);
return user;
}
return null;
})
.catch(this.handleError);
}
auth.service.spec.ts
describe('AuthService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
AuthService,
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend: MockBackend, options: BaseRequestOptions) => new Http(backend, options),
deps: [MockBackend, BaseRequestOptions]
}
],
imports: [
RouterTestingModule
],
});
});
it('should return an Observable<User>', inject([AuthService, MockBackend], (authService: AuthService, mockBackend: MockBackend) => {
mockBackend.connections.subscribe((connection: any) => {
connection.mockRespond(new Response(new ResponseOptions({
body: '{"token": "abc123", "name":"Jeff"}'
})));
});
authService.login('[email protected]', 'password').subscribe(user => {
expect(user.name).toEqual('Jeff');
});
}));
});
Logging the response outputs the following:
Response
body: ReadableStream
locked: true
__proto__: Object
bodyUsed: true
headers: Headers
__proto__: Headers
ok: true
redirected: false
status: 200
statusText: "OK"
type: "default"
url: ""
__proto__: Response
The error Unexpected token ... in JSON at position 1
actually means that JSON.parse
was applied to something that is not valid JSON - a random string or a value that is not a string at all which was coerced to string.
The message Unexpected token o ...
implies that parsed value was most likely an object - which is coerced to [object ...]
string.
The problem is clearly seen here:
Response
body: ReadableStream
locked: true
__proto__: Object
bodyUsed: true
...
Response object here is an instance of global Response
constructor (a part of Fetch API), and the presence of ReadableStream
clearly indicates this.
As it can be seen in Fetch polyfill, all that res.json()
does is applying JSON.parse
to some value
this.json = function() {
return this.text().then(JSON.parse)
}
which is likely new ResponseOptions
object that was erroneously supplied to global Response
constructor, hence the error.
Angular has similarly named Response
class which derives its interface from Fetch Response
but obviously isn't compatible. The problem is that it was never imported and thus global Response
was used instead.
It should be
import { Response, ResponseOptions, ... } from '@angular/http';
Globals are declared in Typescript type definitions, they cannot be undeclared but can be re-declared in custom type definition:
custom.d.ts
declare var Headers: undefined;
declare var Request: undefined;
declare var Response: undefined;
declare var URLSearchParams: undefined;
Using globals instead of imported Http classes of the same name will result in type error:
TS2532: Object is possibly 'undefined'.
Which is a desirable behaviour as long as Fetch API isn't used in Angular app.
This is not an issue for HttpClient
that replaced Http
in Angular 4.3 and doesn't use classes of same names as the ones from Fetch API.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With