Tests: Why, How and When?


Through my past experiences, I have come to realize that tests are unfairly prejudiced against. I will make some assumptions about you:


  • you've been told ever since you were a child that tests are essential to have a reliable source code, although you don't actually see their point
  • you're interested in having a robust code and spotting efficiently bugs when they get in, but what you have read so far about tests does not excite you enough to implement them
  • you're quite confused about the different kinds of tests: in which cases unit testing applies? What's the difference between functional, integration and system testing? How do I know what the most important part of my code to test is?


If you match one of these cases, you're reading the right article. In this article, I'll be reviewing the three main patterns of testing (unitary tests, functional tests, and integration tests) and which development cases they apply to.


Unitary tests

What do they check?

Unitary tests are the lowest level check performed on a code: it asserts that a given object accomplishes the expected action. Here are the two main use cases:

  • check that a function returns the expected result when given a specific set of parameters - it can be either a data, or the rise of an exception, or a specific number of calls to another function,...
  • check that a component renders as expected when passed a specific set of properties


Why and when should you implement them in your code?

Systematically writing tests when adding a new file or function to your project is not a pragmatic practice. As you may have heard, tests are useful to prevent regressions, to detect changes in your component (for a React or Angular context), and develop faster. The questions you have to ask yourself are consequently the following:

  • Are you writing a pure function (i.e. a function that will always return the same result when given the same parameters)? Write a test to cover all the cases your function is supposed to be confronted with! It'll make your function behavior clearer, it'll notify you as soon as your function is ready-to-use, and it documents your function more than an actual documentation could do!

  • if not, have you just developed a component or function that is not supposed to be modified soon? Write a test to have its behavior frozen! Doing so, you will be automatically notified if some code you wrote impacts your component

  • if not, have you just spotted a bug in your application? Write a test to expose the bug right away! It will prevent it from happening again, and you'll have a quicker way to check that the bug is solved


What common mistakes should you avoid?

  • don't implement tests mechanically without asking you why
  • always mock your external calls (DB, libraries,...): a unit test unitarily tests
  • don't put any if or switch case in your test: write one test for each case
  • don't update mechanically your snapshots every time you modify your components - it may be tempting, but you then miss the point of testing them


Integration tests

What do they check?

Integration tests are performed between unit tests and functional tests: they aim at ensuring that parts of your code (already tested independently) keeps working when integrated into an actual system. For instance, here is the difference between unit tests and functional tests on a typical registration() function :

  • unit test: when receiving the set of parameters ('John Doe', 'mySecretPassword'), the registration function calls the createUser mocked database function with the set of parameters (John Doe', 'mySecretPassword', {role: 'BASIC_USER'}), and returns a 200 status code.

  • integration test: when receiving the set of parameters ('John Doe', 'mySecretPassword'), the registration function actually calls the createUser database function, which has been previously provided (by you!) with fake users. Hence, you'll check that your database correctly handles each case: the e-mail already exists, the password does not match the expected pattern, etc.

Why and when should you implement them in your code?

Integration tests mainly aim at preventing regressions in your application - they will hardly help you to develop.

As integration tests require a more complete test framework (a fake database instance for example), people tend to be discouraged to implement them. Wrong excuse! Many test frameworks (like Jest or Ava) provide you with tutorials to help you populating and testing your database.


What common mistakes should you avoid?

  • don't forget to clean up your fake database between your tests - tests must be swappable between them
  • if you use Docker (and I highly recommend you to do so), use the same image for your database tests that you use for your actual project


Functional tests

What do they check?

Functional tests are basically end-to-end tests; nothing is mocked and every action is actually performed on your application. An integration test stands for a complete user scenario - for instance: `As a user, when I enter "jean.dupont@yopmail.com" and "azerty123" in the credentials fields, then if I click on the Login button, I am redirected to the /home page".`


Why and when should you implement them in your code?

Functional tests do not aim at testing a page design, or the presence of all your components on your page. They will rather have the main features of your application frozen, i.e. what your application must be able to do every time you deploy. You should use them to make sure your users won't face regressions on the core features of your product.


What common mistakes should you avoid?

  • don't test your microfeatures with functional tests, it is not worth the time you spend on it
  • don't half-test your feature: if your user is able to enter his credentials and click on the button but cannot log in, your test is pointless


I hope this article helped you to understand how useful and powerful tests are!

Related article: