Unit testing like a Hacker🧪
Tags: opensource, testing, beginners, tutorial
Published: 2022-10-29 02:57:49


## Inspiration
In one of my CS courses, we were taught to write code implementation just enough to pass the unit tests. This is called Test-driven development (TDD) and it is all about reassuring yourself/your team that the code behaves as intended for particular use cases. Primarily, you would have to write the unit tests first and then write the implementation to pass the tests.

Writing unit tests overall and keeping good maintenance on them was not an easy concept to grasp at first, but it is one of the crucial skills I learned as a junior developer. 

### HacktoberTest - haha, it's a pun...

Being in and out with unit tests and dealing with labs/assignments daily actually helped me find proper tickets to contribute to during Hacktoberfest!

My first 2 PRs([1st](https://github.com/tonybaloney/vscode-pets/pull/199) & [2nd](https://github.com/intel/cve-bin-tool/pull/2122#pullrequestreview-1139432365)) were likely `newbie level` and I wanted to contribute to something more `meaningful`, something that would challenge me intellectually. I spent a good week or so stressing over what to contribute to next on the [issues page](https://github.com/issues?q=is%3Aopen+is%3Aissue+archived%3Afalse+label%3Ahacktoberfest), only to stress even more. What became clear to me was that if you are scrolling and scrolling through the issues page just to find the perfect issue for your use case, you're doomed... there are another 20K people doing the same just to get swags. That way, you are probably spending more energy searching than you would have spent on writing a new feature from scratch or a tough bug fix. 

Luckily, issues that required writing unit tests were not as high in demand as other issues, so I was able to find a few of them. [This issue](https://github.com/pavelkeyzik/pr-approve-generator/issues/2) and [this one](https://github.com/pavelkeyzik/pr-approve-generator/issues/7) gave me the opportunity to write unit tests and let me raise my PRs for a project that was indeed meaningful and challenging.  

### Hacking
The project I contributed to was a [web app](https://pr.pavelkeyzik.com/) called [pr-approve-generator](https://github.com/pavelkeyzik/pr-approve-generator) that generates encouraging messages for PRs. It is intended to be used by project maintainers in GitHub to hearten their contributors. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/spoalsyswuqu47qas35a.png)



Every time you click on `Refresh` button, you get a new message that welcomes the PR and encourages the contributor to keep up the good work. The messages are stored in an array and the app randomly picks one of them to display. Here is the function that handles this logic:

```js 
getRandomMessage() {
    const { messagesState } = state;
    const index = Math.floor(Math.random() * messagesState.length);
    let newMessage;
    let newMessagesState;
    if (messagesState.length !== 0) {
    newMessage = messagesState[index];
    newMessagesState = [
        ...messagesState.slice(0, index),
        ...messagesState.slice(index + 1),
    ];
    } else {
    newMessage = messages[index];
    newMessagesState = [
        ...messages.slice(0, index),
        ...messages.slice(index + 1),
    ];
    }
    state.messagesState = newMessagesState;
    return { newMessage, newMessagesState };
},
```
||:--:|| <b>You can check [randomizer.js](https://github.com/pavelkeyzik/pr-approve-generator/blob/master/src/core/randomizer.js) for better understanding</b>

[The first issue](https://github.com/pavelkeyzik/pr-approve-generator/issues/2) I tackled was to write unit tests for the `getRandomMessage` function. The function is responsible for picking a random message from the array and returning it, also it removes the message from the array so that the same message is not picked again. If there are no more messages left in the array, the empty array gets replaced with the messages array again, and so on. This function is called every time the `Refresh` button is clicked. (There is also a new feature recently added to the app that allows you to change emojis using `getRandomEmoji` function and works in a very similar logic described above. I also raised a PR to write a test for this feature [here](https://github.com/pavelkeyzik/pr-approve-generator/pull/17)). 

Unit testing framework was already implemented, using [Vitest](https://vitest.dev/) so I started hacking by setting up a coverage provider to explicitly identify the `covered/uncovered` lines and mentioned this to the maintainer in the [comments](https://github.com/pavelkeyzik/pr-approve-generator/issues/2#issuecomment-1279906403). I used [Istanbul 🇹🇷](https://istanbul.js.org/) for this purpose.

### Unit testing is therapeutic and painful

I started mocking the `messages & emojis array` and called the `getRandomMessage()` with the mocked array. Depending on the index that was picked, I asserted the returned message not to be equal to the new message state since the message was removed from the array (i.e. they need to be unique). 

```js
  it("should always return unique message", () => {
    const messages = ["1", "2", "3", "4", "5"];
    const emojis = ["1", "2", "3", "4", "5"];
    const randomizer = buildRandomizer(messages, emojis);

    const { newMessage, newMessagesState } = randomizer.getRandomMessage();
    expect(newMessagesState).not.toContain(newMessage);
  });
```

Notice that this test follows the `AAA (Arrange-Act-Assert)` pattern. The `Arrange` part is where you set up only the data to be operated in the test.
```js
const messages = ["1", "2", "3", "4", "5"];
const emojis = ["1", "2", "3", "4", "5"];
```
The `Act` part is where you would call the function/s that you want to test.
```js
const randomizer = buildRandomizer(messages, emojis);
const { newMessage, newMessagesState } = randomizer.getRandomMessage();
```
The `Assert` part is the expected outcome, it is dependent on `Act` as the function would evoke a potential response to be asserted.
```js
expect(newMessagesState).not.toContain(newMessage);
```

With this pattern in mind I have written the entire [randomizer.test.js file](https://github.com/pavelkeyzik/pr-approve-generator/blob/master/src/core/tests/randomizer.test.js) and raised a [PR](https://github.com/pavelkeyzik/pr-approve-generator/pull/5). My [second PR](https://github.com/pavelkeyzik/pr-approve-generator/pull/8) was about writing unit tests for [messages.test.js file](https://github.com/pavelkeyzik/pr-approve-generator/blob/master/src/core/tests/messages.test.js) to make sure the following;

- Each message should be unique regardless of the format and emoji used.
- Duplicated messages failing in test
- LGTM message will fail in test regardless of the format

To satisfy these [requirements](https://github.com/pavelkeyzik/pr-approve-generator/pull/8#discussion_r997335163), I used regular expressions to match the format of the message and assert that the message is unique.

```js
it("should have unique messages regardless of the emojis", () => {
  const regex = /([a-zA-Z0-9 ])/g;
  const uniqueMessages = messages.map((message) =>
    message.match(regex).join("").toLowerCase()
  );

  expect(uniqueMessages).toEqual([...new Set(uniqueMessages)]);
  expect(uniqueMessages.length).toBe(messages.length);
});
```

Oh, and I also made sure `LGTM` is not welcome :P

```js
it("should never contain the message LGTM", () => {
  const lgtmMessages = messages.filter(
    (message) => message.toLowerCase() === "lgtm"
  );
  expect(lgtmMessages).toEqual([]);
});
```

Now, the coverage report was proudly showing the covered lines in green with 100% ! This provides value as the report is a visual indication and documentation of the viability of the tests. It gave me a sense of accomplishment and I found it therapeutic to see it was working as it was intended to be.

### Git troubles

During my second PR though, I had trouble with `git branches`. Initially, when I was writing unit tests for `randomizer.test.js` file I had a branch called `tests-for-randomizer` to implement necessary tests for this particular file. After I implemented my work into this branch, for my second PR I created a new branch called `tests-for-messages` to implement tests for `messages.test.js` file using the command `git checkout -b tests-for-messages`. Obviously all the work I have implemented for `randomizer.test.js` came along with the new branch `messages.test.js`. 

I would have to first update the `master` branch and in the current branch `tests-for-messages` rebase to master with `git rebase master -i` (_-i for interactive_) to remove the commits that were not related to `messages.test.js` file. The problem was that I only practiced rebase on my own and it was intimidating to do it in an open-source project. I was afraid to mess up the project and lose my work. I asked some [help](https://github.com/pavelkeyzik/pr-approve-generator/pull/8#issuecomment-1282534140) from the maintainer and he [guided](https://github.com/pavelkeyzik/pr-approve-generator/pull/8#issuecomment-1282548697) me well through the process. So in conclusion, to solve this problem I had to rebase the branch `tests-for-messages` to `master` which removed the commits that were not related to `messages.test.js` file and did a force push to the remote branch `tests-for-messages` with `git push -f origin tests-for-messages`. 

### The pain in unit testing

Ensuring everything is working as intended makes sense in principle, for example, I currently am developing a static site generator in cpp, [palpatine](https://github.com/batunpc/palpatine), and as I develop it I stress about writing unit tests for it. Soon enough whenever a bug occurs I will be writing unit tests before debugging it. While writing unit tests though, I need to keep in mind that they won't stick around forever, my ssg tool is rapidly evolving; refactoring, adding new features, fixing bugs and shipping new releases day by day. That said, the unit tests will be obsolete soon enough and I might end up spending more time maintaining unit tests than actually developing the tool. Thus my philosophy at writing unit tests is to write them when they are actually needed, maybe when the consequences of breaking the code are high or when they are solving a specific problem.

### Conclusion 

Hacktoberfest was a perfect gateway for me to start contributing to open-source projects. Being in touch with the community and learning from senior developers or experienced maintainers is thus far been the most rewarding part of the month.