Skip to content

Instantly share code, notes, and snippets.

@hevans90
Last active January 21, 2020 15:41
Show Gist options
  • Save hevans90/9f999420806870f7201b7cd081cd1b6c to your computer and use it in GitHub Desktop.
Save hevans90/9f999420806870f7201b7cd081cd1b6c to your computer and use it in GitHub Desktop.
marble testing chained `from(promise)`
import { createAction, props } from '@ngrx/store';
export const login = createAction('[Auth] Login');
export const logout = createAction('[Auth] Logout');
export const logoutSuccess = createAction('[Auth] Logout success');
export const logoutError = createAction('[Auth] Logout error', props<{ error: any }>());
import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { StoreModule } from '@ngrx/store';
import { OktaAuthService } from '@okta/okta-angular';
import { Observable, of, throwError } from 'rxjs';
import { marbles } from 'rxjs-marbles/jest';
import { mockOktaAuthProvider } from './okta-auth.mock';
import { login, logout, logoutError, logoutSuccess } from './auth.actions';
import { AuthEffects } from './auth.effects';
jest.mock('jwt-decode', () => {
return jest.fn().mockImplementation(() => {
return { name: 'harri', preferred_username: 'hevans900', email: 'harri@nice.com' };
});
});
describe('AuthEffects', () => {
// tslint:disable-next-line: prefer-const
let actions: Observable<any>;
let effects: AuthEffects;
let oktaAuth: OktaAuthService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [StoreModule.forRoot({})],
providers: [AuthEffects, provideMockActions(() => actions), mockOktaAuthProvider]
});
effects = TestBed.inject(AuthEffects);
oktaAuth = TestBed.inject(OktaAuthService);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
describe('login$', () => {
it(
'should trigger a login redirect with the correct route',
marbles(m => {
const oktaLoginRedirectSpy = jest.spyOn(oktaAuth, 'loginRedirect');
actions = m.hot('--a', {
a: login()
});
expect(oktaLoginRedirectSpy).not.toHaveBeenCalled();
effects.login$.subscribe();
m.flush();
expect(oktaLoginRedirectSpy).toHaveBeenCalledWith('/profile');
})
);
});
describe('logout$', () => {
let logoutSpy: jest.SpyInstance;
beforeEach(() => {
logoutSpy = jest.spyOn(oktaAuth, 'logout');
});
// Override the promises with observables as native promises break the test rxjs scheduler.
const logoutSuccessFactory = (spy: jest.SpyInstance) =>
spy.mockImplementation(() => of(true) as any);
const logoutFailureFactory = (spy: jest.SpyInstance) =>
spy.mockImplementation(() => throwError('logout failed, you are stuck here') as any);
it(
'should call okta logout with the correct route',
marbles(m => {
actions = m.hot('--a', {
a: logout()
});
effects.logout$.subscribe();
m.flush();
expect(logoutSpy).toHaveBeenCalledWith('/');
})
);
it(
'should dispatch a logoutSuccess action on success',
marbles(m => {
logoutSpy = logoutSuccessFactory(logoutSpy);
actions = m.hot('-a', {
a: logout()
});
m.expect(effects.logout$).toBeObservable(
m.cold('-a', {
a: logoutSuccess()
})
);
})
);
it(
'should dispatch a logoutError action on failure',
marbles(m => {
logoutSpy = logoutFailureFactory(logoutSpy);
actions = m.hot('-a', {
a: logout()
});
m.expect(effects.logout$).toBeObservable(
m.cold('-a', {
a: logoutError({ error: 'logout failed, you are stuck here' })
})
);
})
);
});
});
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { OktaAuthService } from '@okta/okta-angular';
import { from, of } from 'rxjs';
import { catchError, map, switchMap, tap } from 'rxjs/operators';
import { login, logout, logoutError, logoutSuccess } from './auth.actions';
@Injectable()
export class AuthEffects {
constructor(private oktaAuth: OktaAuthService, private actions$: Actions) {}
login$ = createEffect(
() =>
this.actions$.pipe(
ofType(login),
tap(() => this.oktaAuth.loginRedirect('/profile'))
),
{ dispatch: false }
);
logout$ = createEffect(() =>
this.actions$.pipe(
ofType(logout),
switchMap(() =>
from(this.oktaAuth.logout('/')).pipe(
map(() => logoutSuccess()),
catchError(error => of(logoutError({ error })))
)
)
)
);
}
import { OktaAuthService } from '@okta/okta-angular';
import { of } from 'rxjs';
export const mockOktaAuthProvider = {
provide: OktaAuthService,
useValue: {
$authenticationState: of(true),
loginRedirect: () => undefined,
logout: () => Promise.resolve(true) as any,
isAuthenticated: () => Promise.resolve(true),
getAccessToken: () => Promise.resolve('access token'),
getIdToken: () => Promise.resolve('ID token')
} as Partial<OktaAuthService>
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment