Don't write Conditional Logic in Tests
Tags: testing, programming
Published: 2022-07-25 15:30:07

[Original Article] (https://www.ethancarlsson.dev/blog/conditionaltestlogic)

## Conditional logic should not go in tests
This is one of the most important rules in
writing good tests.

You should be suspicious of any test that includes constructs like
`if`, `while`, `for`, `try` or anything similar that your language might have.

There are two main reasons for this

### 1. It forces you to read the code

I shouldn't have to read the code to know why a test failed

```typescript
import {expect} from 'vitest';
...
test('JacketAdvisor', () => {
    ...
    if (weatherOutside === 'cold') {
        expect(sut.shouldBringAJacket()).toBeTruthy()
    } else {
        expect(sut.shouldBringAJacket()).toBeFalsy()
    }
}) 

```
The above code is reasonably easy to read, but if the test fails I will get this
message
```
FAIL  path/to/tests > JacketAdvisor
AssertionError: expected true to be falsy
```
and I'll have to open the code to figure out why it failed.
Each case should be handled in a different test

```typescript

import {describe, expect} from 'vitest';

...

describe('JacketAdvisor', () => { 
    test('with cold weather shouldBringAJacket returns true', () => {
        ...
        expect(sut.shouldBringAJacket()).toBeTruthy()
    })
    
    test('without cold weather shouldBringAJacket returns false', () => {
        ...
        expect(sut.shouldBringAJacket()).toBeFalsy()
    })
})
```

This way when the test fails I'll have something like this:
```
FAIL  path/to/tests > JacketAdvisor > with cold weather shouldBringAJacket returns true
AssertionError: expected true to be falsy
```

And I'll know immediately why the test failed. 

### 2. It introduces untested code
This is maybe the more insidious problem. If you have untested logic that you use
to test your code, you have no tests that confirm you are actually making the
correct assertions.

For example if I accidentally set weatherOutside to `'col'` in the second example
above, the first test will fail. But in the second example, this code `expect(sut.shouldBringAJacket()).toBeTruthy()`
will just never run. I won't know that the code was wrong until it hits production
because the test code was wrong and had a bug.

## But I need conditional logic for some assertion

Most of the time when you think you need to add conditional logic what you really
need to do is properly isolate your system under test. But there are exceptions where
it does make sense to introduce conditional logic.

### 1. You're working with legacy code
When it comes to legacy code all rules go out the window. If you need to add a 
bit of logic to set up a test harness and make it possible to safely refactor
that code, then just do it. Don't let the perfect get in the way of the good.

Just remember that it's temporary. When you've refactored the system to the
point where it's components can be tested in isolation test those components and
delete the old tests. 

### 2. Your writing a testing library or a custom assertion
It's actually impossible to have a test with no conditional logic
because under the hood all assertions look something like this:
```typescript
function assertTruthy(val) {
    if (val == false) {
        throw new AssertionError({message: 'expected true to be false'});
    }
}
```
Instead of "Don't write Conditional Logic in Tests" the rule should probably be
"Test code should never have logic that branches beyond one level and should only
branch in order to make an assertion"(of course, that's not a very good title though).

If you need to, you can write your own custom assertions with conditional logic
inside them to use in your tests. This is allowed because unlike a test an assertion
can be tested very easily without your tests becoming absurd.

Just remember to write a test that asserts that your assertion throws the right
exceptions and avoid overly complex branching logic inside those assertions.

These are exceptions though, conditional logic should still be treated as a smell,
you shouldn't write a bunch of custom assertions to get around this rule.