DEV Community

Cover image for Don't Mock What You Don't Own
Maksim Ivanov
Maksim Ivanov

Posted on • Edited on

Don't Mock What You Don't Own

Originally posted on maksimivanov.com

I was refactoring specs of some Rails application when I decided to mock the ImageUploader class of CarierWave. I wanted to be able to check for specific image url. But was it a good idea?

No, Don't Do It

I know it might be tempting to mock libraries that make database or network calls to make specs run faster, but that's not what test doubles are meant for.

Test doubles are ment to help you create practical and convinient interfaces between parts of your application.

mocking

Imagine you have to create some thing that you should integrate in already existing code.

Using test double you can easily check if the interface of the thing (that is not implemented yet) is clear and easy to use. And if not – it's cheap to throw it away and start over.

Now looking from this perspective you should see that mocking third-party thing doesn't make any sense.

But not only it's meaningless…

Wait, What Perspective?

I mean, TDD is not only about testing, it's also about design. Mocking external lib won't help you to create good design, because you don't have any control over it.

The idea is to use mocks as quick and rough prototypes, to see how your thing will be integrated into existing code. If you see that the interface is not very handy – you just write the mock differently, with different methods or with different method signatures.

So you just prototype your interface first and write your implementation later.

Now if you apply this logic to mocking external library (which interface you cannot change), you'll see that it makes zero sense.

Got it? Let's continue…

It's Also Harmful

Most obvious danger is getting false positives (or negatives, depends on perspective). In other words your test will pass where the actual thing won't work. Either because you've mocked it in a wrong way or the library you were mocking changed slightly after an upgrade.

Also by mocking an external lib you are binding yourself to a specific implementation and it will be much harder to change the library in the future.

Another downside is that you might end up with a lot of excessive code that doesn't bring any value. It can also make it harder to understand what is going on in your code.

But HTTP And Database Calls Make My Tests Slow

If that's really a problem – create wrappers around that third party thing.

Don't forget to write integration tests for that wrappers.

As a bonus you'll make it clear what functions of that external lib you really use, and it will be much easier to replace that dependency later.

Summary

In my case I went with using ImageUploader directly, writing a wrapper for it would be just crazy.

I hope this small article will help you avoid this caveat.

If you are interested in further reading on that topic – here are some more articles:

Top comments (10)

Collapse
 
meanin profile image
Paweł Ruciński • Edited

I see your point, but I don't agree with it, at least not entirely.

As you say, TDD is both about testing and designing. To be honest, for me it is about UNIT testing. Not integration one. It is good, when you can clearly distinct these two kinds of testing in your source code.

For example, you are writing about database communication. There is a well known repository pattern which handles that. Giving another layer for db communication provides you an opportunity to test only your code when testing classes which use repository and in other scope you can test whole db communication.

As you wrote, there is also a possibility to extract and test only these methods, which you really need.

I think that a truth lies somewhere in the middle. We should distinguish, if it is possible, our dependencies from 3rd parties, in unit test, test only our code. Also it is good to create integration test exactly for 3rd party libraries, communication etc.

I enjoy reading your article as I am big fan of any kind of testing especially TDD approach. Thanks for sharing.

Collapse
 
satansdeer profile image
Maksim Ivanov

Thanks for the comment.

You have a very good point. I've introduced a test that is tightly coupled to CarrierWave here, which made it kind of integration tests (which I didn't originally intend to do).

So here I've described that mocking would be even worse idea.

But I agree that the better solution would be to have a wrapper.

This article describes not so recent events, but I'll research the possibility of wrapping CarrierWave and ActiveRecord for my future projects.

Collapse
 
markschweiger15 profile image
Mark Schweiger

I agree with Paweł.
From my experience, avoiding mocks on some tests turns them into integration tests and that's not always the goal when doing TDD.
I do like the idea of having wrappers, but that doesn't reduce the need for mocking completely when dealing with 3rd party libs.

Great article!

Collapse
 
alysivji profile image
Aly Sivji

You make an interesting point. I get why wrapper libraries help, but how do you test that library?

What kind of integration tests do you use? How do you test the wrapper's interface with the third party library? Stubs?

Collapse
 
satansdeer profile image
Maksim Ivanov

You should test the wrapper in integration with the actual library. Using Stubs would violate exactly that principle.

There is a great talk where the guy partially covers the topic of wrapping external libs: vimeo.com/80533536

Collapse
 
mjrider profile image
Robbert Müller

just wondering, how do you design, and test the handling of errors and failures from 3rd party code without mocking the responses, because breaking e.g S3 for real is not really an option.

Collapse
 
satansdeer profile image
Maksim Ivanov

You can use dev instance, and for S3 there is actually a whole fake server :-)

But when you work with external libraries/services – you better work with real thing, because mocking it will hide real problems from you.

And if you have a nice wrapper around S3 lib – you won't have to test error handling every time you have to interact with this service. You'll have a set of tests that check if S3 still has the same interface and interacts with you in expected manner.

Collapse
 
hallsamuel90 profile image
Sam Hall • Edited

I think ideally you would have both. During development, it makes no sense to tie your unit tests to anything outside of the architectural boundaries. Taking it a step further I would actually opposite and say "Mock What you Don't Own".

The integration layer should handle verifying the plumbing of components outside of the architectural boundaries.

Collapse
 
lukaszwiktor profile image
Łukasz Wiktor • Edited

cpecific

typo here

Collapse
 
satansdeer profile image
Maksim Ivanov

thanks, corrected