I encountered the identical issue. Instead of utilizing bindNodeCallback()
, I stumbled upon a solution where you can simply enclose the entire call to userInfo()
within Observable.create()
as shown below (NOTE: the outer userInfo()
function is a part of my AuthProvider
class):
userInfo = (token: string): Observable<User> => Observable.create(observer => {
this.auth0.client.userInfo(token, (err: any, user: User) => {
if (err) {
observer.error(err);
}
observer.next(user);
observer.complete();
});
});
Debugging (using Jest)
If you need a unit test for the code above, I make use of the incredible rxjs-marbles
library created by @cartant (who commented in the previous sections). Here are snippets from the relevant parts of my test:
import { TestBed } from '@angular/core/testing';
import { marbles } from 'rxjs-marbles/jest'; // <-- add /jest here for enhancement!
import { Observable } from 'rxjs';
import { StoreModule, Store } from '@ngrx/store';
import { AuthProvider } from './auth'; // my authentication service
// Mocking essential segments of the Auth0 library
jest.mock('auth0-js', () => ({
WebAuth: options => ({
client: {
userInfo: (token, cb) => {
if ( "error" === token ) {
cb(new Error("Profile error!"), null);
} else {
cb(null, {name: "Alice"});
}
}
}
})
}));
describe("Auth0.WebAuth.client", () => {
let auth: AuthProvider;
let store: Store<any>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ StoreModule.forRoot({}) ],
providers: [ AuthProvider ]
});
store = TestBed.get(Store);
spyOn(store, 'pipe');
auth = TestBed.get(AuthProvider);
});
it("should provide an Observable<User> when calling userInfo()", marbles((m) => {
const user = { name: "Alice" }; // matched with mocked value above
const source = m.cold("--a", { a: "dummy-access-token" });
const target = m.cold("--b", { b: user });
const profile = source.flatMap(auth.userInfo);
m.expect(profile).toBeObservable(target);
}));
it("emit an Error on failure while calling userInfo()", marbles((m) => {
const err = new Error("Profile error!"); // matched with mocked value above
const source = m.cold("--a", { a: "error" }); // triggers mock error
const target = m.cold("--#", null, err);
const profile = source.flatMap(auth.userInfo);
m.expect(profile).toBeObservable(target);
}));
});
Some challenges that I faced:
- Ensure to utilize
flatMap()
in your tests! It took me a while to understand its significance
- I was relatively new to marble testing and had to repeatedly refer to the documentation before grasping the concept thoroughly