---
title: "Shrink the Untestable Code With App Actions And Effects"
url: "https://www.cypress.io/blog/shrink-the-untestable-code-with-app-actions-and-effects"
description: "This blog post shows how to shrink untestable code and pushes it to the edges of the applications. The core of the web application will remain easy to check from our tests, while external effects can be observed or stubbed."
---

[Blog](/blog/)[Education: Tutorials](/blog/tag/tutorials)

# Shrink the Untestable Code With App Actions And Effects

February 28, 2019

•

By Gleb Bahmutov

This blog post shows how to shrink untestable code and pushes it to the edges of the applications. The core of the web application will remain easy to check from our tests, while external effects can be observed or stubbed.

The Cypress.io test runner has limitations. For example, when controlling the network requests, Cypress can spy and stub XHR requests only. We will remove this limit when issue [#687](https://github.com/cypress-io/cypress/issues/687?ref=cypress-io.ghost.io) lands, but for now if your application is using `fetch` protocol to access external data - you are out of luck. You cannot see the requests, or wait for them, or stub them, unless you [force the application to drop to XMLHttpRequest](https://github.com/cypress-io/cypress-example-recipes?ref=cypress-io.ghost.io#stubbing-windowfetch). Or can you?

During a typical end-to-end test, the test code loads the page, which triggers the application code. The web app could load some data from remote server so it would execute `fetch` request to the server. By using [App Actions](https://cypress-io.ghost.io/blog/2019/01/03/stop-using-page-objects-and-start-using-app-actions/ "App Actions") your tests can access parts of the application’s inner code. If the web application is structured so that all calls to `fetch` are placed at the _boundary_ of the application, with minimal logic inside of them, then stubbing these calls from the tests really _only stubs_ the `fetch` call.

## [](#Overmind-js "Overmind.js")Overmind.js

Let me show how this works out in practice. Recently a new state management library called [Overmind.js](https://www.overmindjs.org/?ref=cypress-io.ghost.io) came out from the maestro of state management [Christian Alfoni](https://twitter.com/christianalfoni?ref=cypress-io.ghost.io). The library has very interesting pragmatic API, works with multiple frameworks and has a very clear separation between state manipulation and external effects. External effects are methods that do network calls, access storage, ask users for permission to access location, etc - all the things that are **hard** to test, even with native events.

Here is a short example taken from the [Overmind.js examples page](https://www.overmindjs.org/guides/beginner/01_getstarted?ref=cypress-io.ghost.io). I am using the React version, but the same principles apply to every framework - we are going to work with the state directly using App Actions.

> **Source code**
> 
> You can find the final source code for this blog post in the [bahmutov/overmind-demo-1-react](https://github.com/bahmutov/overmind-demo-1-react?ref=cypress-io.ghost.io) repo.

The application is rendering a list of posts fetched from [JSON Placeholder](https://jsonplaceholder.typicode.com/?ref=cypress-io.ghost.io) server. You can find the view component in [Posts.jsx](https://github.com/bahmutov/overmind-demo-1-react/blob/master/src/Posts.jsx?ref=cypress-io.ghost.io) below. It is a “regular” React component that gets everything from `this.overmind` instance.

```jsx
import React from 'react'
import { connect } from './overmind'

class Posts extends React.Component {
  componentDidMount () {
    this.props.overmind.actions.getPosts()
  }
  render () {
    const { overmind } = this.props

    return (
      <div>
        {overmind.state.isLoadingPosts ? (
          <h4>Loading...</h4>
        ) : (
          <div>
            Show count:{' '}
            <select
              id='select-count'
              value={overmind.state.showCount}
              onChange={overmind.actions.changeShowCount}
            >
              <option value='10'>10</option>
              <option value='50'>50</option>
              <option value='100'>100</option>
            </select>
            <ul>
              {overmind.state.filteredPosts.map((post, index) => (
                <li className='post' key={post.id}>
                  <h4>
                    {index + 1}. {post.title}
                  </h4>
                  {post.body}
                </li>
              ))}
            </ul>
          </div>
        )}
      </div>
    )
  }
}

export default connect(Posts)
```

Note that on start, the application fetches the list of posts by calling:

```js
componentDidMount () {
  this.props.overmind.actions.getPosts()
}
```

Let us look at the [overmind.js](https://github.com/bahmutov/overmind-demo-1-react/blob/master/src/overmind.js?ref=cypress-io.ghost.io) file.

```js
import { Overmind } from 'overmind'
import { createConnect } from 'overmind-react'

const overmind = new Overmind({
  state: {
    isLoadingPosts: true,
    showCount: '10',
    posts: [],
    filteredPosts: state => state.posts.slice(0, state.showCount)
  },
  actions: {
    getPosts: async ({ state, effects }) => {
      state.isLoadingPosts = true
      state.posts = await effects.request(
        'https://jsonplaceholder.typicode.com/posts'
      )
      state.isLoadingPosts = false
    },
    changeShowCount: ({ value: event, state }) => {
      state.showCount = event.target.value
    }
  },
  effects: {
    request: async url => {
      const response = await fetch(url)
      return response.json()
    }
  }
})

export const connect = createConnect(overmind)
```

When the application starts, it calls `actions.getPosts`, which sets the `state.isLoadingPosts = true` and then calls _the request effect_. The effects in Overmind is where your application is accessing the outside world. In this case, this is where you fetch the posts from [JSON placeholder server](https://jsonplaceholder.typicode.com/?ref=cypress-io.ghost.io).

```js
effects: {
  request: async url => {
    const response = await fetch(url)
    return response.json()
  }
}
```

See how simple the effects code is? It is minimal - it just executes the `fetch` and gets the JSON result. This code is such a thin wrapper around the elusive network `fetch` method, that if we spy or stub `effects.request` method itself, not much is going to be lost in our tests. Can we stub it?

## [](#Testing-with-App-Actions "Testing with App Actions")Testing with App Actions

Before looking at the effects, let us dispense with simple tests. First, we can set the `overmind` instance on the `window` object so that our tests can easily access it. From the application code:

```js
// overmind.js
const overmind = new Overmind({
  ...
})
if (window.Cypress) {
  window.overmind = overmind
}
```

To make it more convenient for testing, I have added a [custom Cypress command](https://on.cypress.io/custom-commands?ref=cypress-io.ghost.io) to get to this `window.overmind` instance.

```js
// adds custom command to return "window.overmind"
Cypress.Commands.add('overmind', () => {
  let overmind

  const cmd = Cypress.log({
    name: 'overmind',
    consoleProps () {
      return {
        Overmind: overmind
      }
    }
  })

  return (
    cy
      .window({ log: false })
      // instead of .its('overmind') that always logs to the console
      // use ".then" shortcut (but without retry)
      .then({ log: false }, win => {
        overmind = win.overmind
        cmd.end()
        return overmind
      })
  )
})
```

File [cypress/integration/action-spec.js](https://github.com/bahmutov/overmind-demo-1-react/blob/master/cypress/integration/action-spec.js?ref=cypress-io.ghost.io) shows a typical test that dispatches an action via [cy.invoke()](https://on.cypress.io/invoke?ref=cypress-io.ghost.io) on the Overmind instance and observes the changed DOM.

```js
context('Overmind actions', () => {
  beforeEach(() => {
    cy.visit('/')
  })

  it('invokes an action', () => {
    cy.get('.post').should('have.length', 10)
    cy.wait(1000) // for dramatic effect
    cy.overmind()
      .its('actions')
      .invoke('changeShowCount', { target: { value: 50 } })
    cy.get('.post').should('have.length', 50)
  })
})
```

I have added one second wait to make the DOM change noticeable.

Just as easily we can access the state object itself to confirm its value. In test [cypress/integration/get-state-spec.js](https://github.com/bahmutov/overmind-demo-1-react/blob/master/cypress/integration/get-state-spec.js?ref=cypress-io.ghost.io) we are controlling the application through the GUI and are confirming that the state changes in response.

```js
it('changes values in state object', function () {
  cy.get('.post').should('have.length', 10)
  cy.overmind()
    .its('state.showCount')
    .should('equal', '10')

  cy.get('#select-count').select('50')
  cy.get('.post').should('have.length', 50)
  cy.overmind()
    .its('state.showCount')
    .should('equal', '50')
})
```

Great, so what about the `effects.request` call on application start? Can we control it from our tests?

## [](#The-“When”-Problem "The “When” Problem")The “When” Problem

Whenever a page makes a network request at startup, we need to prepare to intercept it _before_ the page loads. For example, if we _could_ intercept `fetch` requests we would write a test like this using [cy.route](https://on.cypress.io/route?ref=cypress-io.ghost.io):

```js
it('fetches data', () => {
  cy.server()
  cy.route('https://jsonplaceholder.typicode.com/posts').as('load')
  cy.visit()
  cy.wait('@load') // asserts the request happens
})
```

But we cannot spy on `fetch` requests yet. So we need to use Sinon ([bundled with Cypress](https://on.cypress.io/bundled-tools?ref=cypress-io.ghost.io)) to spy on `effects.request` method inside `overmind` object.

Hmm, we cannot create a spy before `cy.visit` - the overmind instance has not been created yet!

```js
it('requests data', () => {
  // NO, there is no app and no overmind yet!
  cy.overmind()
    .its('effects')
    .then(effects => {
      cy.spy(effects, 'requests').as('load')
    })
  cy.visit()
  cy.get('@load').should('have.been.calledOnce')
})
```

Ok, we can create a spy _after_ `cy.visit`. No, this does not work either. By then it is _too late_, the request has already gone out.

```js
it('requests data', () => {
  cy.visit()
  // NO, too late, the "effects.request()" has already been called
  cy.overmind()
    .its('effects')
    .then(effects => {
      cy.spy(effects, 'requests').as('load')
    })
  cy.get('@load').should('have.been.calledOnce')
})
```

I placed both of these tests in [cypress/integration/wrong-timing-spec.js](https://github.com/bahmutov/overmind-demo-1-react/blob/master/cypress/integration/wrong-timing-spec.js?ref=cypress-io.ghost.io) for demo purposes.

## [](#Synchronous-callback "Synchronous callback")Synchronous callback

We need to set up spying on `effects.request` _after_ the overmind instance has been created, but _before_ the application fires off `effects.request('[https://jsonplaceholder.typicode.com](https://jsonplaceholder.typicode.com/?ref=cypress-io.ghost.io)')` call. We cannot even register “on” event listener or set a promise, because our code will be queued up to run _after_ the application code, and the request will go out again too quickly before we are ready. But luckily there is a solution. Cypress tests run in the same event loop as the application - the tests just load in different iframe. So we can _synchronously_ install a spy during the application start up.

So our application code could do the following

```js
const overmind = new Overmind({
  ...
})
if (window.Cypress) {
  window.overmind = overmind
  if (window.Cypress.setOvermind) {
    // calls spec function synchronously
    window.Cypress.setOvermind(overmind)
  }
}
```

Our test code should expect this call from the application. We can set our spies right away - and they will be ready when the application executes `effects.request` method call.

```js
it('can spy on request method in effects', () => {
  Cypress.setOvermind = overmind => {
    cy.spy(overmind.effects, 'request').as('request')
  }
  cy.visit('/')
  cy.get('@request')
    .should('have.been.calledOnce')
    .and(
      'have.been.be.calledWithExactly',
      'https://jsonplaceholder.typicode.com/posts'
    )
})
```

Beautiful, we can spy on the network request effect. And we can stub it - and return fake data from a [fixture](https://on.cypress.io/fixture?ref=cypress-io.ghost.io).

```js
it('can stub the request method in effects', () => {
  // load fixture with just 2 posts
  cy.fixture('posts').then(posts => {
    Cypress.setOvermind = overmind => {
      cy.stub(overmind.effects, 'request')
        .as('request')
        .resolves(posts)
    }
  })
  cy.visit('/')
  cy.get('@request')
    .should('have.been.calledOnce')
    .and(
      'have.been.be.calledWithExactly',
      'https://jsonplaceholder.typicode.com/posts'
    )
  cy.get('li.post').should('have.length', 2)
})
```

## [](#Elegance "Elegance")Elegance

The previous `Cypress.setOvermind = ...` code works, yet we can use a nice little trick to catch the object property set operation, since we know the name of the property. Again, the trick I am about to show only works because Cypress tests have direct access to the application under test.

```js
// application code is back to simply
// setting "window.overmind" property
if (window.Cypress) {
  window.overmind = overmind
}
```

We are going to define a property on the `window` with a setter, and it will be called immediately when the application does:

```js
Cypress.Commands.add('onOvermind', set => {
  expect(set).to.be.a('function', 'onOvermind expects a callback')

  // when the application's window loads
  // prepare for application calling
  // window.overmind = overmind
  // during initialization
  cy.on('window:before:load', win => {
    // pass overmind to the callback argument "set"
    Object.defineProperty(win, 'overmind', { set })
  })
})

it('catches overmind creation', () => {
  cy.onOvermind(overmind => {
    // when overmind is JUST created
    // we set the spy on effects.request
    cy.spy(overmind.effects, 'request').as('request')
  })
  cy.visit('/')
  cy.get('@request').should('have.been.calledOnce')
})
```

We can do more advanced things, like actually perform the request, and then modify the returned data before passing back to the application. Let us set the title of the first post to “My mock title”:

```js
it('can transform post titles', () => {
  cy.onOvermind(overmind => {
    cy.stub(overmind.effects, 'request')
      .as('request')
      .callsFake(url => {
        // call the original method
        return overmind.effects.request.wrappedMethod(url).then(list => {
          // but change the result
          list[0].title = 'My mock title'
          return list
        })
      })
  })
  cy.visit('/')
  cy.contains('.post', 'My mock title')
})
```

Unfortunately, if we want to access the `window.overmind` instance in our tests, our `defineProperty` shortcut needs both `set` and `get` settings.

```js
cy.on('window:before:load', win => {
  // pass overmind to the callback argument "set"
  let overmind
  Object.defineProperty(win, 'overmind', {
    set (value) {
      set(value)
      overmind = value
    },
    get () {
      return overmind
    }
  })
})
```

But now we can write interesting tests. For example, during loading, our state has `isLoadingPosts = true`, and the UI shows loading message. Once the `effects.request` finishes, the state changes, and the loading message goes away. Can we confirm it?

Yes, for example we can stub it the effect, and resolve it after a delay. While the response is delayed using [Bluebird `delay`](http://bluebirdjs.com/docs/api/promise.delay.html?ref=cypress-io.ghost.io) method (Bluebird is [bundled with Cypress](https://on.cypress.io/bundled-tools?ref=cypress-io.ghost.io)) we can check the state and the UI. Then delay ends, and from the test we can check the state and the DOM again to confirm that they show posts.

```js
it('delay the response to effect', function () {
  // there are two posts in the fixture
  cy.fixture('posts').then(posts => {
    cy.onOvermind(overmind => {
      // when effect.request happens
      // we are going to delay by 2 seconds and respond with our data
      cy.stub(overmind.effects, 'request')
        .as('request')
        .resolves(Cypress.Promise.delay(2000, posts))
    })
  })
  // let's roll
  cy.visit('/')
  // page makes the request right away, nice
  cy.get('@request')
    .should('have.been.calledOnce')
    .and(
      'have.been.be.calledWithExactly',
      'https://jsonplaceholder.typicode.com/posts'
    )
  // while request is in transit, loading state
  cy.contains('Loading') // UI
  cy.overmind() // state
    .its('state.isLoadingPosts')
    .should('be.true')

  // load should finish eventually
  cy.overmind() // state
    .its('state.isLoadingPosts')
    .should('be.false')
  cy.contains('Loading').should('not.exist') // UI
  cy.get('li.post').should('have.length', 2)
})
```

The test reads long, but the Cypress Test Runner shows what is going on beautifully.

You can find this test in the spec file [cypress/integration/visit-overmind-spec.js](https://github.com/bahmutov/overmind-demo-1-react/blob/master/cypress/integration/visit-overmind-spec.js?ref=cypress-io.ghost.io).

## [](#Final-thoughts "Final thoughts")Final thoughts

To review

-   [App Actions](https://www.cypress.io/blog/2019/01/03/stop-using-page-objects-and-start-using-app-actions/?ref=cypress-io.ghost.io "App Actions") allow end-to-end tests to directly interact with the application’s inner code, rather than always going through the DOM.
-   A good state management library like [Overmind.js](https://www.overmindjs.org/?ref=cypress-io.ghost.io) makes it easy to put all hard to test code into very thin and isolated “effects” methods.
-   Our tests can synchronously inject spies and stubs into application code to reach into the state object, actions methods and even wrap the effects methods, shrinking the untestable code to a minimum.