Unit-testing – How should I test “Glue Functions” without testing that “the code I wrote is the code I wrote”

drytddunit testing

I usually write my code in a test driven style. I write tests as specifications and then my code. It's great and useful.

I always try to ignore implementation when testing and only test behaviour. I don't care how it gets done, just that it got done. I find this especially easy for functional programming.

Now here is the problem I have found. I have an app that is written in a functional style. All of the unit tests are nice, clean, behavioural tests. I only ever check output and don't do things like "did you call this function?".

At some point however, I start needing "glue" functions. I'll consider these functions that don't introduce a lot of functionality and largely just call my other existing functions. Perhaps chaining a bunch together or whatever feature it may be.

How do I test these glue functions?

I ask because I want to avoid two main things as much as possible:

  • I don't want to test them by simply mocking what they do and seeing if specific functions were called.
  • They have a desired output I want, but usually this output is just a series of outputs from other functions that are already tested. I don't want to repeat myself and just "re-test" those inner functions to see if my glue function called them.

Hopefully that makes sense. Here is an example (written in pseudo code):

func1 (x) => x + 1;
func2 (x) => x * 2;

glue (x) => [func1(x), func2(x)];

Here would be a simple way of testing these functions.

testFunc1 () => expect func1(2) == 3;
testFunc2 () => expect func1(2) == 4;

testGlue () => expect glue(2) == [3, 4];

So obviously, glue has an expected and predictable behaviour I want to model. I know that in this example these tests might be ok. So consider instead that the outputs of func1 and func2 are not simple numbers but much more complicated objects.

In such a case, implementing the checks that glue output the correct objects would be tedious AND it would be totally duplicated from the individual tests of func1 and func2. This also leads into the next issue.

Instead consider:

testGlue () => expect glue(2) == [func1(2), func2(2)];

This certainly seems better. But I think it is still flawed. While this means I am not repeating my test code it now instead "tests that the code you wrote is the code you wrote" (as opposed to what the behaviour is). Again, in such a small example it's not an issue so pretend that within glue a few variables are swapped around and yada yada is done so that to test it in this way would require my test to also set up the variables like such. Then we would be basically copying the code from the function to check if func1 and func2 were called with the correct variables leading to repetition and testing that "it's the code you wrote".

If a larger example is needed to showcase such results just let me know and I will get one. Hopefully there is some good discussion to be had here.

I anticipate someone to answer "don't use glue functions" and to that I preemptively ask, "what's the alternative method?".

EDIT:

So I am beginning to think that an alternate question that would also give me the answer I want is this.

Consider that the output of func1 and func2 is something too big to feasibly have as a hardcoded value in the test. Maybe it's an object or something.

Does writing my test of glue as:

testGlue () => expect glue(2) == [func1(2), func2(2)];

No hold on, I must clarify something. Obviously the above test is absolutely stupid. It is just "the code I wrote is the code I wrote".
We MUST imagine that the function does more than this. In a real world scenario glue would do some processing of x before passing it around. The order of the array might matter. And however many other options. So maybe I'm checking that glue(2)[0] = func1(3) instead (pretend there is further processing to it).

In such a case, is it still considered bad practice to use the output of a function as something to test against (even though that function is tested somewhere else)?

Best Answer

Boring structural code doesn’t need isolated testing.

Test interesting code. That code has a behavior. Nail down the behavior you expect and not only will your code likely be correct, it’ll be easier to read.

But keep that interesting code away from the boring structural code. Do that and I’ll be able to read it and trust it without a test that isolates it.

Now if the boring structural code is part of a chain of integrated peripherals and behavior objects then fine, throw an integration test at it. If you’d like to test if I’m full of it, break the structural code and see how long it takes someone to find the problem.

Don’t waste time solving non problems. At best you’re only amusing yourself. At worst you’re actually making it harder to refactor the code.

Remember: it’s not that every function needs a test. It’s every behavior.

Related Topic