Home > database >  Should we always unit test for specific values
Should we always unit test for specific values

Time:10-03

In unit tests, when a function calls another function when should we test for the output/return value of a function as opposed to the testing whether a function calls another function.

In my case I have a wrapper method around moment

weekdayWrapper(): number {
    return moment().weekday();
}

Should we always test that specific results of the weekdayWrapper always produce the correct result? In this case I wonder whether that's necessary. We trust that moment will give us the correct result so we only need to ensure that moment's weekday will be called. On the other hand, the method definition tells us it expects a number and so would be meaningful for us to test whether the return value is correct or not. Testing both seems to be the most robust but I'm wondering whether testing specific values in simple cases (i.e. wrapper methods) are really necessary.

CodePudding user response:

What should be tested depends on the function.
Consider a function

greaterThan1(integer: Int) {
  return integer >1
}  

Here I would unit test integer values 0 (false), 1 (false) and 2 (true). It does not make sense to test any negative integer, nor to test integers > 2, since the result is anyway clear.

Now consider a function

isPrime(integer: Int) {
// suitable code
}  

Here a unit test can only be done for, say, the first 1.000.000 integers (without the even integers > 2). And this should be done, even without a guarantee that the function is always correct.

So it depends on whether you consider some cases as completely clear (so that they don’t have to be tested), or worth testing.

CodePudding user response:

When unit testing, you should always be asking yourself what happens when the implementation inevitably changes? Test what the function promises to do, not how it does it.

What does this function promise to do?

If it is documented as "a wrapper around moment().weekday()" then yes, simply test that it calls weekday. It's called weekdayWrapper, so there you go.

If it is documented to "get the day of the week as a number. The start of the week is defined by the locale", then no, do not test that it calls weekday. This makes an assumption about the implementation of the function. It is glass-box testing, so called because it treats the function as a box you can see inside. If the implementation changes the function might continue to work as defined, but the test breaks.

Glassbox tests should be avoided whenever possible, they are a barrier to change. Instead, prefer black-box testing where you make no assumptions about the implementation. Then the implementation can freely change so long as the function continues to do what it promises.

This may lead to the apparently absurd test of checking that weekdayWrapper() returns the same value as moment().weekday(), but that test will continue to work if the implementation of weekdayWrapper() changes! You're not testing moment().weekday(), we trust that works. You're testing that weekdayWrapper() returns the day of the week and you happen to be using a trusted function to check that.

Alternatively, set a specific time and locale for the test and check that weekdayWrapper() returns the correct day for that time and locale. How you do this depends on your testing framework.


To really answer the question, one must ask why have such a thin wrapper at all? Typical reasons include...

  1. You need to pass around a callback function which returns the day of the week.
  2. You want to hide an implementation detail so that it may change.

Neither one requires it to exactly call moment().weekday(). Considering that MomentJS is a legacy project, #2 is a strong possibility. You might want to swap out MomentJS for another framework without having to hunt down where it's used all over the code. Then again, it's pretty easy to hunt down calls to moment() so the wrapper may be of dubious use.

Questioning the design of what is being tested is all part of the testing process.

  • Related