You might know the Arrange-Act-Assert pattern (AAA pattern) as a tool for writing unit tests. After some adaptation, it can be a great tool for writing high-level tests like end-to-end tests as well.
The AAA-pattern
A test written with the AAA pattern consists of three phases:
Arrange — Act — Assert
If you practice Business-Driven Development (BDD), these phases are called Given-When-Then.
- Arrange. Also known as Setup.
Arrange the state of your application/code. Prepare everything you need so you can Act. Preparation can include seeding the database, preparing an API request, creating a class instance, creating a new user account, etc. - Act.
Do the thing you want to test. - Assert. Also known as Expect.
Check if the thing you did in the Act phase worked as expected. If your test fails, it should fail here.
High-level tests
High-level tests live high in the test pyramid. An example of high-level tests is end-to-end tests.
Low-level tests live low in the test pyramid. An example of low-level tests is unit tests.
Repeat Act and Assert
In low-level tests, each phase is done exactly once per test. This works for low-level tests because of the inherently small Arrange phase. The small Arrange phase allows you to split a process with multiple steps into multiple tests. However, high-level tests generally can’t get away with this.
In a high-level test, you need more stars to align for your test to pass. Because of this, your Arrange phase can get quite big, including large time investments like creating a new user, reseeding the entire database, logging in to the GUI, etc. In this situation, you can repeat the Act and Assert phases. A high-level test might look like:
Arrange — Act — Assert — Act — Assert — Act — Assert
Arrange — repeat_as_needed(Act — Assert)
This way, you Arrange once while testing multiple related things. Your test will now cover multiple things. In low-level tests, it’s an anti-pattern for a test to cover multiple things. In high-level tests, however, it can be the only way to get things done in a reasonable timeframe.
How often you repeat the Act and Assert phases depends on the balance between test duration, maintainability, understandability, and debuggability. A good balance between these factors can improve your test automation.
If you test all features in a single test, you repeat the Act and Assert phases many times. This will be good for test duration but awful for maintainability, understandability, and debuggability. On the other hand, if you never repeat Act and Assert, you will run into test duration and maintenance issues due to the required repetition in the Arrange phases.
As a rule of thumb, repeat the Act and Assert phases as few times as feasible. What is feasible depends on your specific situation.
Speedrun Arrange
Not all parts of a test are equally useful to us. Ideally, our tests would only use the Act and Asserts phases. We don’t actually care about the Arrange phase. Arrange only exists so we can Act later. Because of this, you should spend as little time as possible in the Arrange phase.
Arrange — Act — Assert
In the Arrange phase, do as little as possible in the GUI, use fake data whenever possible, skip entire process steps if you can get away with it, and generally take every shortcut you can. This way you can quickly get to the parts you actually care about: The Act and Assert phases.
A common example is test users. Imagine we want to test the password change functionality with a fresh user account. In this test, we only care about the password change functionality, not the user account creation. You can create this user with the registration process in the GUI, but a better approach for this test is adding it directly to the database. If that proves difficult, use an API to make the user account instead. “But Sander, the registration process needs to be tested too!” Yes, but in a separate test. In this test, we don’t care about it.
The Cleanup phase
In a low-level test, like a unit test, your full application state is in memory. This means that (most of the time) the application is state automatically cleaned up after your test. With high-level tests, this is not the case. In high-level tests, you might want to include a fourth phase:
Arrange — Act — Assert — Cleanup
In this phase, you clean up the data you made during the test. This can include things like: Remove test data from the database, delete created files, etc.
The Cleanup phase is not the only way to clean up test data. Other approaches include:
- Scheduled cleanup
Clean up in a separate scheduled process. Unlike the Cleanup phase, you only need to set this up once regardless of the number of tests. For example, imagine your tests create new test users. Every day, a cleanup process deletes test users older than 7 days. - Dangling state
Clean up right before the next test starts, making cleanup part of the Arrange phase. For more details, see the Cypress documentation on embracing dangling state.
Conclusion
Arrange-Act-Assert (AAA-pattern) is not just for unit tests. It works for any test, including high-level tests. You can repeat the Act and Assert phases as needed, take shortcuts in the Arrange phase, and add a Cleanup phase. With these tweaks, you can use the AAA pattern to create tests for even the most complex scenarios.