Search code examples
angularunit-testingjasminekarma-jasmineauth0

How do I bypass auth0 login for Angular testing?


I'm trying to write tests for my Angular / Auth0 application. When there is nothing in localStorage or their token is expired, it directs the user to the auth0 domain for signin. However, I'm having a difficult time mocking a user to bypass the auth0 login screen.

app.component.ts:

export class AppComponent {
  constructor( public auth: AuthService) { auth.handleAuthentication() }
}

auth.service.ts:

export class AuthService {
  authConfig = {
    clientID: //omitted for privacy,
    domain: //omitted for privacy,
    callbackURL: window.location.origin + '/home',
    apiUrl: //omitted for privacy
  }

  auth0 = new auth0.WebAuth({
    clientID: this.authConfig.clientID,
    domain: this.authConfig.domain,
    responseType: 'token id_token',
    audience: this.authConfig.apiUrl,
    redirectUri: this.authConfig.callbackURL,
    scope: 'openid profile email',
  })

  constructor() { }

  public login(): void {
    this.auth0.authorize()
  }

  public logout(): void {
    localStorage.removeItem('access_token')
    localStorage.removeItem('id_token')
    localStorage.removeItem('expires_at')
    this.login()
  }

  public handleAuthentication(): void {
    if( this.isAuthenticated() ) return

    // if the token is old, logout
    if(localStorage.getItem('expires_at') && !this.isAuthenticated()) { 
      this.logout()
    } else {
      this.auth0.parseHash((err, authResult) => {

        // if we didn't just log in, and we don't have an expiration token, login
        if(authResult === null && !localStorage.getItem('expires_at')) {
          this.login()
        }

        if(authResult && authResult.accessToken && authResult.idToken) {
          this.setSession(authResult)
        }
      })
    }
  }

  private setSession(authResult: any): void {
    // Set the time that the Access Token will expire at
    const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime())
    localStorage.setItem('sub', authResult.idTokenPayload.sub)
    localStorage.setItem('email', authResult.idTokenPayload.sub.split('|')[2])
    localStorage.setItem('access_token', authResult.accessToken)
    localStorage.setItem('id_token', authResult.idToken)
    localStorage.setItem('expires_at', expiresAt)
  }

  // Check whether the current time is past the access token's expiration time
  public isAuthenticated(): boolean {
    const expiresAt = JSON.parse(localStorage.getItem('expires_at'))
    return new Date().getTime() < expiresAt
  }
}

app.component.spec.ts:

import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { MaterialModule } from './material.module';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ 
        RouterTestingModule,
        MaterialModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    console.log('inside create app in app spec');
    expect(app).toBeTruthy();
  });
});

auth.service.spec.ts:

import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AuthService } from './auth.service';
import { DebugElement } from '@angular/core';

describe('AuthService', () => {
  let service: AuthService;
  let authSpy;
  let fixture: ComponentFixture<AuthService>;
  let debugElement: DebugElement;

  beforeAll( () => {
    const expiry = JSON.stringify(new Date().setHours(5));
    localStorage.setItem('sub', 'sub')
    localStorage.setItem('email', '[email protected]')
    localStorage.setItem('access_token', '1234')
    localStorage.setItem('id_token', '1234')
    localStorage.setItem('expires_at', expiry);
  })

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [ RouterTestingModule ],
      providers: [ AuthService ]
    });

    service = new AuthService();
    authSpy = spyOn(service, 'isAuthenticated').and.returnValue(true);
  });

  afterAll( () => {
    service = null;
  })

  it('should be created', () => {
    const service: AuthService = TestBed.get(AuthService);
    console.log('inside auth service spec');
    expect(service).toBeTruthy();
  });

  it ('should return true when all localStorage set', () => {
    //this is not returning true even if it gets to run, which 70% of the time it doesn't
    expect( service.isAuthenticated() ).toBeTruthy();
  });
});

Now, if I'm just running this in the browser without tests it runs fine. Logs in user, routes them to /home. But the tests are taking me to the auth0 login screen (running maybe 2-6 tests before) and if I use a login, it says 404: /home.

I can't seem to find any good testing docs for auth0, Angular, and routing... Any help much appreciated


Solution

  • Instead of running auth.handleAuthentication() in the constructor, run it in the ngOnInit method. This allows you to create an instance of the component and test its functionality without being sent to login.

    Most Angular documentation I've read recommends this approach, and to limit the amount of code that's done in a constructor in any service or component.