Your application might be a layered cake of historical data. Often old records are missing pieces because at first the web application never asked for them, or never validated them. The current web application might be much stricter with its user inputs, never allowing incomplete data to be entered.
Take a TodoMVC application. It might check the input text, disallowing empty titles.
// application code
addTodo(e) {
// do not allow adding empty todos
if (!e.target.value.trim()) {
throw new Error('Cannot add a blank todo')
}
e.target.value = ''
this.$store.dispatch('addTodo')
this.$store.dispatch('clearNewTodo')
}
Note: you can find the application source code and example tests described in this blog post in the testing-workshop-cypress repository.
How will the web application handle Todos that somehow do have title: " "
? Will the web application handle this data edge case gracefully? Or will the application lock up? Or will it most likely survive and show the list in some weird way?
Confirm empty titles cannot be entered
First, let's confirm that web application UI do not allow entered just blank space characters as a title. In the above code we see that the application is supposed to throw an exception - this behavior is not perfect, but we can test it.
The test fails - because our application is throwing an error, which causes Cypress test to fail by default
We expect the application to throw an error, thus we set an error event listener, following the documentation page linked from the error message https://on.cypress.io/uncaught-exception-from-application.
it('does not allow entered empty title', () => {
cy.visit('/') // loads 2 todos
cy.on('uncaught:exception', e => {
// only ignore the error message from blank title
return !e.message.includes('Cannot add a blank todo')
})
cy.get('input.new-todo').type(' {enter}')
// confirm the blank todo has not been added
cy.get('li.todo').should('have.length', 2)
cy.get('li.todo label').should('not.have.text', ' ')
})
The test passes - but we need to be careful - if the application does NOT throw an error at all, our exception handler will never be reached. Properly accounting for the expected number of assertions would side-track the discussion at hand, I suggest you read the blog post When Can the Test Stop? for additional details.
Stubbing network calls
Let's test this. First, we need to know how our application gets the initial list of todos. The DevTools Network panel shows a call to GET /todos
the application performs on load. This is how the front-end is loading the initial list of items from the database.
The response object is an array of objects with title, id and completed properties.
Click the "Response" tab to see the original JSON text that you can copy.
Save the above text as the file cypress/fixtures/empty-title.json
[
{
"title": "write code",
"completed": false,
"id": "8400230918"
},
{
"title": " ",
"completed": false,
"id": "4513882152"
}
]
Now let's write a test - except instead of going to the backend, the initial GET /todos
request will be intercepted by Cypress, and the fixture data will be returned using the cy.route command.
/// <reference types="cypress" />
it('renders empty title', () => {
// prepare to stub network request BEFORE it is made
// by the loading application
cy.server()
cy.route('/todos', 'fixture:empty-title')
cy.visit('/')
})
The test runs - and the application is showing the empty title, albeit imperfectly
Now you can style the application if necessary or take any other precautions against empty titles. The important point is that the built-in Cypress Network stubbing allows you to test how your application is handling the cases of data that are impossible to create via the current UI.
Full control with App Actions
To test a data edge case we can reach directly into the application at run-time and create a data item using what we call App Actions. From the application's code store the reference to the app
on the window
object when the application is running inside a Cypress-controlled browser like this
// somewhere in your application code
const app = ...
if (window.Cypress) {
window.app = app
}
Because Cypress test code runs in the same browser window, you can access the application's window
object, then grab the app
property - and now the tests can manipulate the app reference directly. For example, our web application is implemented using Vuex, thus there is app.$store
object with actions the application code dispatches. But our test code can use the same object to dispatch its actions, bypassing any UI restrictions.
it('handles todos with blank title', () => {
// stub the initial data load
// so the application always starts with 0 items
cy.server()
cy.route('/todos', []).as('initial')
cy.visit('/')
// make sure the network call has finished
cy.wait('@initial')
// bypass the UI and call app's actions directly from the test
// app.$store.dispatch('setNewTodo', <desired text>)
// app.$store.dispatch('addTodo')
cy.window()
.its('app.$store')
.invoke('dispatch', 'setNewTodo', ' ')
cy.window()
.its('app.$store')
.invoke('dispatch', 'addTodo')
// confirm the application is not breaking
cy.get('li.todo').should('have.length', 1)
.find('label').should('have.text', ' ')
})
In the above test, we start with zero items after the initial Ajax call completes - by stubbing it and waiting for it. Then we dispatch two actions directly to the application's Vuex data store, creating a Todo item with a single space character as a title. Since the App Action is invoked directly against the data store, the UI check is bypassed. Finally, the test confirms the single Todo with the blank label is displayed.
In this blog post I showed how to test edge cases in the data that your application might have to handle; the edge cases that are impossible to recreate by going through the user interface anymore, because the application has tightened the input data validation rules. We have confirmed that blank titles can no longer be entered, but if a Todo item with a blank text does get shown it does not break the application (aside from looking unprofessional).