Settings

Theme

Ask HN: Writing unit test mocks vs. real dependencies?

5 points by kamalkishor1991 5 years ago · 9 comments · 1 min read

Reader

I like to use real things as much as posible but I find lot of people using too much mocks sometime. Can you provide some advise on how to decide mocks vs real services/dependency? Could not find any good blog on the this specific topic.

dragontamer 5 years ago

If A depends on B depends on C, then:

* C should have a unit test proving its capabilities.

* B is allowed to have a unit test that depends on C (because C's unit tests prove its correctness).

* A is allowed to have a unit test that depends on B (similar to the B/C case).

---------

It seems like mocking was a way to "cut" the dependency between A and C. In particular, you usually get huge "dependency chains" between your code base. A depends on B, depending on C, depending on D... depnds on E, F, G, H, I... etc. etc. Pulling in all of this code for a unit test seems backwards. IMO, the solution isn't to use mocking, but to refactor your code to reduce the dependencies.

But if the code is outside of your control (ie: owned by another team, or even a 3rd party), maybe mocking is the best option.

"Flattening" your code dependencies, reducing the "height" of your layers will simplify your code logic and software engineering. But its not always an option in reality.

------

If you have a circular dependency: A depends on B and B depends on A, then:

1. Find a way to break the circular dependence. Invent a new class C: Maybe A depends on C, B depends on C, and C represents the shared interface between the two. Once broken up in this manner, A depends on C (and A unit tests depend on C), then B depends on C (and B unit tests depend on C), and finally you write unit tests for C itself. No mocking needed.

2. If #1 cannot happen, Both A and B must be unit tested together as a singular component. Trying to split it up using mocking is a fool's errand.

Jugurtha 5 years ago

Well, I had to use mocks when interacting with a device through Bluetooth Low Energy. It's a hardware device that can't be in a CI/CD pipeline.

I pushed the hardware part into a class, injected it as a dependency to client code so I could materialize the BLE connection whenever I wanted.

The binary encoding/decoding wasn't tied to the class and was handled by a codec I wrote to decouple packet handling from the device. You only had to feed it a YAML file I wrote to model the device communication protocole. I read the manufacturer's specs and came up with a schema for rx/tx padding, etc.

When we changed devices, we just had to give another YAML file to the codec and the code worked.

All this to say that it was really hard to test before code refactoring and loosening the dependency on the actual device.

Once we had a nice interface, you could write a MockBLEDevice class even by inheriting from the real class, just changing the actual BLE connection to return something else and keeping everything else the same.

seanwilson 5 years ago

There's no best way and time is a limited resource. Weigh up the time it's going to take and the pros/cons for your specific project. If you've done this and think mocking is going to take up too much time for not enough benefit then you've made a good tradeoff.

goatcode 5 years ago

Test what you are testing. Are you testing a method or function? Mock its inputs. If your test of a function fails because an API or a database is not available, that's approaching an integration test. If there's a question of whether there are too many mocks being used, the dev may not have thought out a testing strategy well enough. Be certain of what you're testing.

That's what I usually go by.

  • dragontamer 5 years ago

    Lets say that some codebase is using a custom bitonic sort (a highly-parallel, SIMD sorting algorithm) instead of a standard library sort.

    So you have:

        array<Foo> myFunc(Baz b){
          someArray = bar(b);
          bitonicSort(someArray);
          return someArray[0:2]; // Return the top 3 elements after sorting
        }
    
    So we're trying to test myFunc(). We have dependencies on bar() and bitonicSort(). There may be hidden dependencies on Baz class functions (maybe bar() calls Baz.func()).

    Now we have a variety of mocking strategies. We could mock the Baz class. We could mock bitonicSort(), we could mock bar() function.

    But does such mocking really make our testing life easier? I argue it doesn't. Our unit test really should just be written as the following:

        Baz initializeBaz(){
          // A consistent initialization across many
          // different tests
        }
    
        void unitTest(){
          Baz b = initializeBaz();
    
          array<Foo> a = myFunc(b);
          assert(a.size() == 3);
          assert(a[0] == Foo(1, 2, 3)); 
          assert(a[1] == Foo(4, 5, 6)); 
          assert(a[2] == Foo(7, 8, 9)); 
        }
    
    Easy peasy. No mocking needed. Compare now if we mocked out the Baz class, or the bitonic-sort method. It just becomes an unwieldy mess.

    There's no need to overthink things. Maybe have 2 or 3 different, reasonable, instantiations of Baz. If a bug is discovered in the future, create a Baz that represents that bug.

    If bitonicSort isn't working correctly, its not our job (as writers of myFunc()) to care about bitonicSort's correctness. We are allowed to assume correctness upon our dependencies. It is also NOT our job to test bitonicSort. If we happen to catch a bitonicSort bug, that's good... but remember that bitonicSort will have unit tests of its own.

    • goatcode 5 years ago

      Yes, I had a brain fart when I wrote my original comment. I think I should have meant to say mock dependencies (that we have created, not necessarily external ones, unless they're something like network calls), not inputs.

      • dragontamer 5 years ago

        bitonicSort() is a hypothetical dependency. So at least, by my reading of the comment, it seems like you'd be recommending to mock out bitonicSort().

        • goatcode 5 years ago

          Only if bitonicSort is something that you'd written and would test elsewhere, or it reaches out to resources outside of the immediate software in which its used.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection