François Constant logo

How to prevent bugs in an SPA?

May 2020

Single-page applications are more likely than “classic” websites to have bugs. This article details why that’s the case and how to prevent these bugs.
This is a copy of the original article I wrote at interaction.net.au.

A single-page application (SPA) is an app which runs in a Web browser. In a “regular” website, each screen is a different web-page. In a single-page application, the complete app is loaded within one page.

For example, Google Maps is a single-page application. When searching for a place in Google Maps, the full page is not reloaded in the browser. Instead, the map itself and some of the information displayed on the screen are updated. These updates are done via a programming language called Javascript.

At the Interaction Consortium, we generally build regular websites with a framework called Django. For single-page applications, we often use Django with React (or Django with Angular or Django with Elm - we do really like Django). In a single-page application using Django and React, Django handles the admin & API - the backend - while React handles the customer-facing site - the frontend.

Single-page applications are more likely to have bugs

Single-page applications are more error-prone for various reasons. User flows are more complicated; this is because we do not load a new page at every single user-action. The application has more components “talking” to each other. There must be at least two components: Django & React for example. Last but not least, the Javascript language makes it very easy to introduce bugs (there are various technical reasons).

As it is easier to introduce bugs in a single-page application, avoiding them is even more crucial than with classic builds.

We must “test”. Preventing bugs is essential.

Building a single-page application should be fun and interesting for everyone involved. It is unfortunately not always the case. To me, the key is the ability to update an app without causing any bugs (new or recurring). In other words, it is imperative that “everything built so far still works”.

Without the ability to safely update an app, the following vicious circle happens:

  1. developers work on a new feature
  2. developers notice improvements they could make to previous features but they will not do it for fear of breaking something
  3. code quality diminishes, because things aren’t getting improved, just extended
  4. building that new feature takes a bit longer than the previous one
  5. developers do not notice any issue and update the app
  6. a different feature - which seems to be unrelated - stops working
  7. client notices the bug and the delay
  8. client’s trust in the developers' capacity to deliver a working product diminishes
  9. project managers and QA testers add the new bug to their growing list of things to check. They also start second-guessing their developers
  10. developers fix the bug and are under increasing pressure. They get even more scared of changing anything since there is one more bug that must never happen again
  11. developers update the app with the bug-fix and are now ready to work on the next feature
  12. developers work on a new feature
  13. etc.

To sum it up, with each new feature: code-quality reduces, productivity reduces, client and manager trust diminishes, testing time increases and developers' anxiety increases.

To avoid this vicious circle, it is imperative to avoid bugs and technical debt (technical debt is basically a quick dirty-job which will create extra work in the future, more details here).

The tools must support developers in making the app more and more stable as they build it. The tools must allow the developers to improve it and tweak it as the requirements evolve. Developers must be able to improve the code and add features while being certain that everything built so far still works.

With that guarantee:

  1. developers work on a new feature
  2. testers, UX designers and developers can focus on the task at hand
  3. developers can safely reuse previous code parts
  4. developers can also improve the code as it grows and their understanding of the project increases
  5. developers confidently build the feature quickly
  6. developers update the app with the new feature
  7. client finds no broken features and appreciates the prompt delivery
  8. client trust grows and communication improves
  9. project managers can focus on the big picture

This is the desired virtuous circle. To achieve it, developers need automated tests.

Testing methods

Manual testing

Manual testing is something you have probably done many times before. For example, if you want to check the login screen, you would create a user via the admin and check that the user can only login with their correct password. Doing these checks involves two manual tests:

1.Data Add "Alice", a user, via the admin.
2.Action Opens a browser, goes to the login page and enters Alice's email and an incorrect password.
3.Expected result You see a message on the page ("incorrect password").
1.Data Add "Alice", a user, via the admin.
2.Action Opens a browser, goes to the login page and enters Alice's email and the correct password.
3.Expected result You are now logged-in as Alice and redirected to Alice's dashboard.

In a regular application, you would not have two tests, you would need tens or hundreds of them. Could you imagine anyone checking all of these properly at every single release? That would be insane.

Automated-testing (the only sane way)

An automated test is the same as a manual test except that it’s run by a computer. An automated test is some code that usually does three things: it sets the current data, performs an action as a fake user and checks the expected result.

Automated tests need to be written once only. With these, developers can check in a few seconds that the login page works as planned. If a few months later the website is entirely re-skinned, the first test above would warn developers if someone forgot to include the error messages on that login page.

With tests, developers can improve their code without worrying about breaking anything. As they can freely improve the code, they can update names, break it down, re-organise it, etc.

Tests are also the only realistic way to prevent a bug from reoccurring. When an issue is found, developers can add a test to reproduce the issue and fix it. With that test in place, you can be sure that the same bug will never happen again (without being noticed first by the developers). There is no other way to be sure. You cannot expect someone to retest the same patterns at every single release manually.

Basically, testing is the tool that allows the virtuous circle mentioned above to happen.

Other ways

They are plenty of ways to limit bugs:

  • picking and consistently using various coding standards
  • breaking down the code into smaller blocks
  • regularly improving and reorganising the code (technically called refactoring)
  • code reviews
  • keeping the user interface simple
  • tracking bugs
  • etc.

While all of these techniques (or best-practices) are great; most of them are made easier with proper automated-tests and some of them are almost impossible without tests.

Conclusion

Single-page applications are more likely to break than regular websites. Preventing new and recurring bugs is essential for the whole team building the application. The only sane way to do so is to write automated tests. There are a variety of methods to develop these tests which are discussed here.