rd31415
rd31415

Reputation: 301

Stubbing out Angular services in Cypress

I have this Angular web application I want to run e2e tests on a mocked out REST API. I can stub out my network requests to my REST API easy enough, but the authentication is using a third-party provider (Cognito using Amplify).

Now I want to stub out the Angular service that wraps the authentication.

In Angular I have

@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {
  some methods

  isSignedIn(): Observable<boolean> {
    ...
  }
}

I want to stub the isSignedIn()-method. My first attempt looks something like this:

import {AuthenticationService} from "../../src/app/authentication.service";
import {BehaviorSubject} from "rxjs";

context('albums', () => {

  it('get albums', () => {
    cy.stub(AuthenticationService,'isSignedIn').returns(new BehaviorSubject(true));
  }
}

Cypress/Chrome then complains it cannot find AuthenticationService on that location. How do I solve this?

Upvotes: 26

Views: 4618

Answers (2)

Yuqiu G.
Yuqiu G.

Reputation: 345

I basically agree what has been said. Cypress is a framework independent ui testing tool. Unlike Jasmin/Jest that can work with angular/react to mock services, it works differently.

With that being said, it has problems aka need certain work arounds executing tests cross domain when involving OIDC, OAuth authentication flows.

Maybe you can try looking into cookies / sessionStorages before and after successful login under chrome devtools, if you can get past the authentication flow:

cy.window().then(window => {
  window.sessionStorage.setItem(key, value);
})

Upvotes: 1

The Fabio
The Fabio

Reputation: 6250

There are a few issues with your test

1 - Cypress itself is not executed within the browser, it is a wrapper application that commands the browser. It uses JS language, but on the wrapper not withing the browser. It can send JS commands to the browser though, as I will explain below.

2 - the AuthenticationService class in your cypress test is different from the one used by angular... for two reasons:

  • When we import a class into the Cypress test, Cypress will use its pre-processor to build a new js class out of the ts file; This class will be a different object from the one angular creates with its pre-processor.
  • the class import into the test will be loaded in the cypress scope (not the browser's one where angular is loaded)

3 - even if AuthenticationService was the same scope as the angular one, it would still be incorrect... what you need is the instance of this class from within Angular's scope/zone.

Worry not, Cypress allows you to reach into the Browser's scope with its Window function:

cy.window()
      .then((window) => { // the window here is the browser's
        window['console'].log('Hi there from Cypress scope'); // this will appear in the console window within cypress
      })
});

And we could provide a reference to the service from within angular's scope to the browser's window. For example, from using your AppComponent like this

export class AppComponent {
  constructor(..., authenticationService: AuthenticationService) {
    window['authenticationService'] = authenticationService;
  }
}

and using cypress's Window function to get the browser's window object, your test could then stub the service:

cy.window().then((window) => {
  const serviceFromAngularScope = window['authenticationService'];

  cy.stub(serviceFromAngularScope,'isSignedIn').returns(new BehaviorSubject(true));
});

Hopefully this gets you started

Upvotes: 3

Related Questions