DEV Community

Max Baumann
Max Baumann

Posted on • Updated on

Testing an Akita-Angular Application with Cypress

Note: This post was written seven months ago

Cypress is one of the easiest ways to test your Angular application. But because it is not tied to any Angular API it is hard to look "under the hood" of your tested app. However directly manipulating the internal state of it can make testing even easier. This post will show you a way to achieve this.

Unfortunately, we need to add a bit of overhead to our Application, this is marginal though.

Get state in Cypress

To write a binding for Cypress we need to create a function that needs to be called in the constructor of each of our Akita Queries. Make sure to pass the query itself to it using this. Cypress provides a global window.Cypress variable we can use to determine whether we are in a Cypress testing environment.

cypressBinding.ts:

export function queryCypressBinding(query) {
    if (window.Cypress) { ...  } else { ... }
}
Enter fullscreen mode Exit fullscreen mode

app.query.ts:

export class AppQuery extends Query<AppState> {
    constructor(protected store: AppStore) {
        super(store);
        queryCypressBinding(this); // <-- Add this line to every Query
    }
}
Enter fullscreen mode Exit fullscreen mode

Our goal is to provide a field that allows access from Cypress. I decided to use the Class name for that. Every time the State changes this field should get updated. We can do this the Akita way using query.select() which will listen for every state-change.

export function queryCypressBinding(query) {
    const name = query.constructor.name; // e.g. AppQuery
    // @ts-ignore
    if (window.Cypress) { 
        // @ts-ignore
        query.select().subscribe(_ => window[name] = query.getValue()); // updates the field with new state
    } else {
        delete window[name]; // to make sure we dont leak state in production
    }
}
Enter fullscreen mode Exit fullscreen mode

Nice! Using this we can test our state in Cypress like this:
sometest.js:


it('should to sth', () => {
    cy.visit('http://localhost:4200/');
    // do stuff
    cy
        .window() // get app's window variable
        .its('AppQuery') // get store
        .its('somevalue') // this depends on your store
        .should('exist') // do whatever testing you want here
});
Enter fullscreen mode Exit fullscreen mode

Manipulate state in Cypress

We now have read-access to our state. But how can we dispatch actions from our testing suite? You might have guessed it, we expose our service to Cypress. So let's write another function to do so and call it in every constructor of our services.
cypressBinding.ts:

export function serviceCypressBinding(service) {
    const name = service.constructor.name;
    // @ts-ignore
    if (window.Cypress) {
        console.log('testing environment detected adding ' + name);
        // @ts-ignore
        window[name] = service;
    } else {
        delete window[name];
    }
}
Enter fullscreen mode Exit fullscreen mode

app.service.ts:

export class AppService {
  constructor(private store: AppStore, private query: AppQuery) {
    serviceCypressBinding(this);
  }
}
Enter fullscreen mode Exit fullscreen mode

Put this to use like this:
anothertest.js:

it('should manipulate stuff', () => {
    cy
        .window() // get app's window variable
        .its('AppService')
        .invoke('update', {
            somevalue: 'Hello World'
        });
    // obvserve changes
});
Enter fullscreen mode Exit fullscreen mode

or call a function on your Service:

it('should manipulate more stuff', () => {
    cy
        .window() // get app's window variable
        .its('AppService')
        .invoke('removeAllTodos'); // call your function
    // obvserve changes
});
Enter fullscreen mode Exit fullscreen mode

Thank you for reading

If this was helpful or you find any better solution let me know!


Twitter: @fosefx
Github: Fosefx

Top comments (1)

Collapse
 
netanelbasal profile image
Netanel Basal

You can now access it directly from the window object in dev mode. datorama.github.io/akita/docs/conf...