Circle-Circle: A Development Retrospective On Testing
Tags: webdev, react, testing
Published: 2024-01-23 15:50:00

Hello, this article is just me sharing my learning experience with testing a frontend application. I'm just a beginner on testing, I hope I can get some feedback on what I learned.

I set out to learn development with a focus on testing, resulting in the creation of a Reddit clone called circle-circle. You can check it out [here](https://circle-circle.vercel.app/). The source code is also available [here](https://github.com/rickyxyz/circle-circle).

Tech Stack:

- ReactJS and Tailwind
- Firebase

Testing Tools:

- Vitest for testing Firebase rules
- Playwright for e2e testing

So, what did I learn from this experience?

## Test First? Code First?

The first hurdle I encountered is trying to figure out which to write first? The test or the code? In retrospective, I feel so dumb about this part, since the answer I came up with is so simple. 

> Do whichever is fastest without compromising on the value test brings.

If I think writing the implementation first and then writing test to check if I implemented the function correctly is faster, then so be it.

If I am confused on how a feature should work, then write the test 
 first to figure out what to do instead of wasting time over thinking about the unclear implementation.

If I am confused on how to test and implement a feature what should I do? Write pseudocode or something you know about the feature on a piece of paper if necessary. The go figure out what you can write, or just continue writing what the feature should do until something you can code starts to make sense.

In short, for me it does not matter which comes first, the test or the implementation as long as the feature is complete and the functionality is covered by some sort of test.

## Requirement And Testing

There are times during development when some requirement changes. For example, a register form does not have a confirm password field, but then I decided to add a confirm password field to it. In this case I needed to change both the implementation code and the test code. 

What I have learned from this is, that you need to use your head to determine when the test needs to be changed when the requirement changes. I think tests are derived from the requirements of the software, so test should cover most if not all requirements, and when requirement changes the tests will follow.

## What To Test?
The next hurdle I encountered was determining what to test and not to test. The answer I came up with are these:

### Is it an external library?

From what I've read on the topic, external functions are not tested. For example, Firebase `signInWithEmailAndPassword` function implementation is not in the circle-circle's code. So it is not tested.

### Will/Has other test already cover this functionality?

I don't want to write and maintain more tests if I can get away with less tests. So, for example if a functionality can be or is already covered by a higher level test, I won't write the lower level test.

For example, the `onSubmit` in create circle form calls `createNewCircle` function.

```ts

  function onSubmit(data) {
    ...
    createNewCircle(data);
    ...
  }
```

For this case I did not test the `createNewCircle` function in an isolated settings, since the create circle functionality is already tested in e2e test.

If the e2e test succeeds then the underlying function that it calls must be working properly, and vice-versa.

However, there are probably edge cases that I have not encountered yet that will require me to test both. 

### Does the function have a clear input and output that a computer can assert?

Since most of this project is frontend, most of the code works with the more "visual" aspect of the application such as height, width, style, color, etc. Now, how do you define a test for something like height or color? I don't think doing automated testing for the more visual aspect is the right approach. Because we human sees the visual differently than a computer.

Take for example, a button. We want the user to understand a button is something they can click.

![An image of a button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4zhx22opn1l8xmx45wij.png)

Visually, we can show something is clickable/interactable through design, but how do we define that in computer language? Thus for outputs that is difficult or cannot be defined to a computer, a human should test it. In this project, I implemented this sort of visual testing for components using Storybook.

An example of something with defined inputs and outputs is the login functionality. For this there are multiple inputs (test cases) and clear expected outputs:

1. Empty inputs -> show input errors
2. Wrong credentials -> show login errors
3. Correct credentials -> successful login

While still a bit abstract, these are things we can define to a computer through a series of automated action and assertions. However, extra attention must be placed in the assertion, since we do not want to keep changing the test every time we change the implementation, but we also don't want the test to be so loose that it lets errors to slip through.

### Is it too simple? Does the test helps to understand the function?

For a simple function, take this function for example,

```ts
function doThing(a) {
  if(a > 10) return 'more than 10';
  else if(a > 5) return 'more than 5';
  return 'less or equal to 5';
}
```

How would one write test this function, we would probably write 

```ts
// pseudocode
expect doThing(11) to be 'more than 10'
expect doThing(10) to be 'more than 5'
etc...
```

However the function is so simple, that the input and outputs of the function is visible in the implementation code. Essentially, the implementation is saying the same thing as the test code. I think writing test for the example above is just creating more code to maintain.

Things do get interesting however when the code gets messy and more complexity is added (the code is written badly on purpose).

```ts
function doThing(a) {
  if(a > 10) {
    if(a % 2 == 0) {
      return 'more than 10 and an even number'
    }
    return 'more than 10 and an odd number'
  }
  else if(a > 5) {
    if(a % 2 == 0) {
      return 'more than 5 and an even number'
    }
    return 'more than 5 and an odd number'
  }
  else if(a % 2 == 0) return 'less or equal to 5 and an even number';
  return 'less or equal to 5 and an odd number';
}
```

Now what the `doThing` function does is not so easy to discern anymore right? Now, would writing test for this function says a different message that the implementation code? I would say it will.

Although for most, they can understand what is going on in the function, for some it may look confusing at first glance. Thus writing test could probably helps to understand the code. For example:

```ts
// pseudocode
expect doThing(11) to be 'more than 10 and is an odd number'
expect doThing(10) to be 'more than 5 and is an even number'
etc...
```

While my explanation works for simple function like the example above, **I'm still just talking from my ass here and still have not encountered more complex function that breaks my explanation, so this explanation could break down at any moment.**

## More Granular Test

When writing test, I find it is more useful for the test to be more granular. Meaning instead of writing a test for "Route can only be accessed by authenticated user", you can write multiple more granular test "Unauthenticated cannot access this route" and "Authenticated user can access this route". I find this more helpful, since it makes it clearer which of the expected behavior is failing. 

## Is All Of This Testing Worth It?

While I am still very inexperienced, I think having test for medium to larger sized project is worth the hassle. True that needing to write tests means more things to code, but I think the time spent writing the test will pay for itself.

When I was trying to update a feature, I can just run the test to check if the update breaks any feature.

During development of a new feature, I can just write test first and then run the test each time I want to know of the implementation is working or not using a single test command of testing manually.

> Thank you for reading to the end.
> I am still a newbie in software development, so any feedback is appreciated.