Arrange Act Assert or Table-Driven Testing?

After sharing our perspective on the “what” and “why” of unit testing in our opening guide, we wanted to move onto the next question of how to concretely write unit tests. There are many libraries that provide frameworks for writing unit tests, but in order to write truly good ones, we believe that it’s important to understand the key syntaxes: Arrange Act Assert and Table-Driven Testing.

In this article, we’ll cover an introduction to each approach, as well as the pros and cons of each. Let’s start with Arrange Act Assert.

Arrange Act Assert

Arrange Act Assert is the industry standard for writing unit tests. It provides an essential template and logic for structuring unit tests in a way that is easily understandable to other developers. So how does it work?

Say you have a simple addition function, such as below:

function sum(a, b) {
   return a + b

If we were to explain in English what we want a test to do, it would be “I want the sum of 3 and 4 to be 7, otherwise my test should fail.” This translates into the following in Javascript:

expect(sum(3, 4)).toBe(7)

If this test fails, it would mean that either there is a bug in my function, or the test is not up to date / testing an old behaviour and should be updated

The Arrange Act Assert approach helps you to break down the test from a logic perspective and ensure that you’re writing it in the right way. It follows human logic, so that the tests that you write are easier to read and understand. In Arrange Act Assert, the test above would be written as follows:

let a = 3               // [ARRANGE] We prepare "a"
let b = 4               // [ARRANGE] We prepare "b"
let result = sum(a, b)  // [ACT]     We call our actual function that does some computation
expect(result).toBe(7)  // [ASSERT]  We verify that the function behaved as expected

As a reminder, unit tests are intended to be on the simplest building blocks of code. While you might have multiple “Arrange” or “Assert” lines, you shouldn’t have more than one “Act” line.

What about Table-Driven Tests?

Arrange Act Assert is an excellent way of writing tests that are easy for other developers to understand, but there are some downsides to it. It is more verbose: as shown in our illustration above, it writes four lines of code for something that could be represented in one line. Imagine how long the test file would be if we needed 5 or 10 tests for a function, and the work it would take to update it if we slightly changed the mechanics of the function.  

Luckily, there is another option: Table-Driven Tests.

In Table-Driven Tests, all inputs and expectations (Arrange and Assert lines) are written in a table, just like in a spreadsheet. The above example would look like this:

const tests = [
   { a: 3, b: 4, want: 7 },
   { a: 0, b: 1, want: 1 },
   { a: 2, b: 2, want: 4 },

tests.forEach(({ a, b, want }) => {
   expect(sum(a, b)).toBe(want)

The human logic behind this would be “for each line in my table, I want the sum of column 1 and column 2 to be column 3, otherwise my test should fail.”

The main benefit of Table-Driven Tests is efficiency. In one glance, you can see what is “essential” in the test. What lives in the “forEach” are the mechanics, you only need to read it once, as it won’t change between your test cases. You can just focus on your input and expected values rather than the syntax. This approach is also quicker when you make changes, for example changing from “sum” to “add”. In a test file written in Arrange Act Assert with 10 test cases, you’d have to update the syntax 10 times, compared to only once in Table-Driven Testing.

So which is better; Arrange Act Assert or Table-Driven Tests?

This is the topic of raging debates within our team Ponicode. On one hand, Arrange Act Assert is easier to read from a human perspective, and is the market standard. On the other hand, Table-Driven Testing gives a great global view and is more efficient when making changes.

Both of these approaches fulfill the eight criteria of any good unit tests. We think Arrange Act Assert is better on #8 (Easy), but Table-Driven Tests is better on #6 (maintainable) and #7 (fast).

Regardless of which method you choose, we believe that the important thing is that both of these are great at keeping unit tests simple. In the Arrange Act Assert method, there should only be one Act line at a time. And for Table-Driven Testing, the fewer columns in the table, the more legible it is.

Or is this a broader philosophical question?

Perhaps this is a broader philosophical debate about where we should be going as developers. Should developer tools generate code that resembles what most human developers write today (i.e. Arrange Act Assert)? Or should we admire and aspire to the beautiful simplicity and efficiency of machines, and aim to code more like them (in Table-Driven Testing)?

Humans vs. machines – therein lies the question of eternal debate within Ponicode.

Start automating your unit tests today