Last active
January 21, 2020 15:41
-
-
Save hevans90/9f999420806870f7201b7cd081cd1b6c to your computer and use it in GitHub Desktop.
marble testing chained `from(promise)`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }>()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: '[email protected]' }; | |
}); | |
}); | |
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' }) | |
}) | |
); | |
}) | |
); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }))) | |
) | |
) | |
) | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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