Why avoid mocks

mocks

#1

Hi all

I am reading the phoenixframework book and says in the testing section, that we should avoid mocks for testing why?

Thanks


#2

José - the creator of Elixir - explained it beautifully here:


#3

Even it is written by José I am not entirely agreeing with the gist of it.

Don’t get me wrong, The abuse of mocking is there and especially a decade and more ago mocking was a fashion, had lots of traction and was - also helped by tools - “state of the art”. That we can abuse it should not let us throw it out completely.

For my own work it helps me to write - or at least think, because documentation is not so my thing :slight_smile: - what the test is actually testing. Then it will be clear that sometimes we are testing third party libraries or system libraries that should have been tested elsewhere. For example, a function that should return a timestamp and just calls a system datetime library returning the “time.now” as ticks does not need to be tested. We assume that the system library works.

For this trivial example we do not need to check the output to look for error conditions. But what alternatives do we have for more sophisticated functions? The general thinking is stubs and dependency injection. But when we
we do this, what are we testing then? tThe implementation of the injected function/module/process? Who is testing the stub?

Of course, the same applies to the mock which will be based on a mocking library, but again we hope - and sometimes in case of open source libraries we can check - that it was tested thoroughly.

So my opinion is that we should define as a community waht went wrong in the first place with mocking, what can we do to avoid it and what are the right use cases to apply it. Just saying “Forget it, it’s bad” does not help.


#4

Hi Kujua,

as far as I remember the general idea is that (to) mock, the verb, is bad and mock, the noun, is good.

An example would be when we make an HTTP call to the GitHub-API to authorize a user via OAuth:

  • If we mock (verb) the “calling an HTTP interface” part, that is bad design.

  • If we wrap our GitHub API in a module (MyApp.GitHub.API) and use a mock (noun) of that module for testing (MyApp.GitHub.TestAPI), that is okay.

Maybe @josevalim can shed some light whether or not my recollection of this is correct?


#5

Firstly, the “verb/noun” distinction has a root in OOP and I believe it is not relevant in Elixir, mocking or not.

Secondly, José’s article is bringing the options of stubs (changing them in the configuration) and testing the real thing. (There is a discussion about the difference of mocks [the "nouns] and stubs, but that is not important here).

So again, what are we testing when we change MyApp.Twitter.HTTPClient to MyApp.Twitter.InMemory? The latter uses an implementation that is in fact the same as something I define in a mocking library, only adding my own potential bugs.

The article talks about dependency injection - which is based on “interfaces” in other languages and is re-defined as “protocols” in Elixir. Both are OO concepts. I never liked dependency injection because it changes the design of a program to accomodate testing. In the functional paradigm we should aim at modules/functions that are free of side effects and dependency injection is a side effect.

One of the weaker points of Elixir is the (getting closer relationship) to Ruby (on Rails) which are a OO language (and an OO framework), Ruby itself is an hybrid language at best. Look at the re-assignment of variables (which is just a workaround of immutability) and the compiler warnings in 1.3 of Elixir for certain use cases.


#6

Hard to argue with that.


#7

@rrrene :slight_smile:

Actually it is not to argue, I really would like to have a discussion about this. Testing itself (TDD or something else) is a good theory, in practice it falls foul of deadlines, project management demands and developer’s (my) own laziness. I think as an industry we don’t have an answer and the huge number of failing or not performing projects shows this.

“Mocking” is just a side battle field and I like to get into it…


#8

I would strongly disagree that dependency injection is an OO concept. In fact I will even say that dependency injection is bread and butter of functional programming. How? Higher order functions. What is a higher order function if not a function where we “inject” the dependency - the code that executes some algorithm or performs some computations.

Injecting whole modules is no different, not really. A module is just a set of functions. Passing a module is a convenient way of passing multiple named functions. Having said that, passing the dependency through application environment is indeed a side-effect and may be not considered a good style, but it’s very simple and convenient.

When it comes to testing and it’s presence or not - I think this depends hugely on the company. I’m fortunate to work in one that requires thorough testing of all the code - code that is not tested is simply not shipping to production. In fact, working on Phoenix APIs, I rarely start the application - most of the time I work just on the tests.


#9

Firstly, the “verb/noun” distinction has a root in OOP and I believe it is not relevant in Elixir, mocking or not.

That’s pointing to the wrong direction. First of all, OOP and FP are not mutually exclusive domains. They are properties shared by both paradigms. I will come back to this later on. The “verb/noun” guidance was not meant to be at the abstraction level but a guidance on how you are expressing your intent in the code.

The latter uses an implementation that is in fact the same as something I define in a mocking library, only adding my own potential bugs.

That’s one of the points of the linked article. You can use mock libraries as long as they are helping you build mocks rather than dynamically replacing existing components. If your mock library is leading you down the former path, then please go ahead! The article is far from a call to “forget mocks because they are bad”.

The article talks about dependency injection - which is based on “interfaces” in other languages and is re-defined as “protocols” in Elixir. Both are OO concepts.

Elixir protocols are not interfaces. Behaviours would be the closest thing to interfaces. However, typed contracts are not exclusive to OO languages. Protocols build on top of behaviours to add data-type polymorphism (a form of ad-hoc polymorphism). Polymorphism is not a concept exclusive to OO as well. Protocols in Elixir provide the same kind of polymorphism as Haskell type classes.

That brings me to the first point: why does it even matter if it is an OO concept? Being an OO concept doesn’t invalidate it nor implies it is a bad concept. In fact, the paper that introduced type classes references polymorphism in OO languages multiple times and does a good job in explaining how type classes address issues commonly found in OO implementations. Similar concept with different implementations.

That’s why I said this discussion is pointing to the wrong direction. Maybe there is a valid argument against those constructs in the context of testing but “because it is OO” is certainly not the reason (nor it is true).

I never liked dependency injection because it changes the design of a program to accomodate testing. In the functional paradigm we should aim at modules/functions that are free of side effects and dependency injection is a side effect.

If by side-effect you mean the functional definition of side-effects, dependency injection is not a side-effect. It would only be a side-effect if the code you are invoking contain side-effects. In languages like Haskell you can do dependency injection via higher order functions or type classes without involving a monad.

On the other hand, I completely understand how “dependency injection” can be a source of confusion. The page for dependency injection in Wikipedia is quite convoluted and I find it personally hard to see how that would map to “passing a function or a module as argument” in Elixir.

Finally the article also answers why I don’t personally agree with the “dependency injection changes the design of a program to accomodate testing”. A test is a consumer of your code like any other API. If your code is hard to test, it will very likely be hard to extend. Sometimes that’s fine, we don’t want all code to be extensible, that would be a nightmare to reason about. But if you feel like you need to make the code more extensible to properly test it, it is very likely the extension will be useful beyond testing. The point of the blog post is to help you reason about those trade-offs rather than find one size fits all solution.


#10

I partially agree. If the value never changes, it will be side-effect free. That’s exactly what happens when the application environment is read at compile-time rather than runtime. The behaviour at runtime is guaranteed to be the same.


#11

You cannot reassign values in Elixir. While it looks like rebinding it is not.


#12

I would also say that avoiding side-effects is not the same thing as no side effects. Side effects are not bad. Unintentional side effects are bad.


#13

I know, it is not re-assigning. - This is why I said “workaround for immutability”. It looks like.


#14

I am sorry. It was your use of the phrase “Look at the re-assignment of variables” which lead me to think that perhaps you were misunderstanding Elixir’s behavior.


#15

No worries. Should have set it in quotes, but in the “heat of the argument” … :slight_smile:


#16

@josevalim Thanks for the clarifications from your point of view.


#17

There is a lot of information, could someone make a summary.
Thanks


#18

I would rather use https://github.com/archan937/mecks_unit … (async) mocking using the Erlang :meck library. I don’t want to compromis (by altering my initial code and making things interchangeable) because I just want to unit test my code.


#19

Rebinding is exactly what it is. Perhaps you meant it is not reassignment???


#20

That’s interesting, I’ll give it a try!