hauleth

hauleth

Guidance Counsellor

I wanted to write down some of the main guides I use when writing Elixir tests, so I have summarised them in blogpost.

Furthermore, I think that mocking must be destroyed.

Showing Posts 41 to 32

johns10davenport

johns10davenport

Mocks are brittle if calling an external service. Less so if you’re calling your own functions, but then you can just call your own functions. Definitely my least used test artifact at this point.

DI is preferable. I have an in memory file system fake I inject into an environment abstraction in my main app. It’s fine.

I really prefer to use the cassette approach at the boundary though. It’s the most reliable and least brittle paradigm imo.

funboy

funboy

I have the impression that many people encountered really bad mock usage early in their programming careers and ended up hating mocks because of that. That was my story too - I hated mocks so much that at one point I reduced them to zero.

Later, I had to learn how to use them properly so they wouldn’t hurt me. It’s not an easy art.

Mocks are just a tool, and like any other tool, they should be used for their intended purpose.
When used correctly, they can be very helpful.

I think the same story applies to things like DI. For the record, I love DI <3, but like any other tool can be overused, and I’ve seen DI misused to the point where a function was passed through dozens of layers just to be used deep down the call stack, and usage&refactoring that later was a nightmare (that was before AI :grinning_face_with_smiling_eyes:).
But we don’t say that DI should be destroyed because of that.

That’s my few cents :slightly_smiling_face:

mudasobwa

mudasobwa

Creator of Cure

Here is how I do test that FSM starts as expected in Finitomata tests:

mock
|> allow(parent, fn -> GenServer.whereis(fsm_name) end)
|> expect(:after_transition, transition_count, fn id, state, payload ->
  parent |> send({:on_transition, id, state, payload}) |> then(fn _ -> :ok end)
end)

{:ok, _fsm_pid} = Finitomata.start_fsm(id, impl, name, payload)

assert_receive {:on_transition, ^fsm_name, ^entry_state, ^payload}, 1_000

My tests are fully based on mock (here I test that after_transition callback has ever been called.)

mudasobwa

mudasobwa

Creator of Cure

Sure I do.

Mocks are a perfect flow controller for interprocess communication. One might both totally prevent flakiness provoked by race conditions in tests and introduce a race condition to test it in particular.

Also, mocks might simulate whatever unexpected behaviour, from timeouts to spawning dozens of concurrent processes in between of request-response loop.

Mocks themselves are a very powerful mechanism to test the code under different circumstances. I have sophisticated mocks, as an opposite to stubs.

hauleth

hauleth OP

The question is - do you need mocking library for that at all. Because that is what I also want and what I do. It just happen, that for this, I do not need any library at all.

mudasobwa

mudasobwa

Creator of Cure

I don’t think that’s really necessary.

Don’t get me wrong, I admire the work done on Repatch. It’s a piece of code, simultaneously elegant and complex. I love it.

I just cannot resist the feeling it’s everything but a mocking library. My coffee machine makes a perfect coffee. My vacuum cleaner sucks (disregard this sentence, I just needed to have complained to someone.) I frankly don’t need a coffee machine to be grinding the grains, I do it myself. Same goes to adding milk. Who ever drinks coffee with milk, huh?

That said, I want a mocking library that allows me to mock behaviorally declared injectable dependencies, period.

Maybe it’s just me, I dunno.

Asd

Asd

It is one, it provides functionality of both Mox and Protomox, and a bunch of other features (like patching functions) too.

See Repatch.Mock — Repatch v1.6.1 and Repatch.Expectations — Repatch v1.6.1 for more info. These two modules basically recreate what Mox does, but with better performance, better isolation and no hacks like patching cover.erl in runtime in order to make coverage work

I am not a developer who thinks that I have to guide my library users somewhere, I just create a best tool there is and then users are free to use it for the best or for the worst. It’s called “freedom”, you should try it sometime.

For example, I haven’t seen a tool which can isolate application env in different tests, but Repatch can and it just works.


UPD, I will update the Repatch documentation with examples on how to use it in the style of Mox

mudasobwa

mudasobwa

Creator of Cure

Eh. But one doesn’t use mock like that. The ‘consider “mock” to be a noun, never a verb’ statement by José should have been carved in stone.

And one doesn’t mock DateTime because DateTime is not behavioural. Poor examples demonstrating that mocks can be abused wouldn’t prove that mocks are of no good. I mean, I could have written a library allowing mocking def/2 and defp/2 macros, but if you used it, you should not probably complain that mocks in genertal behave weird.

Proper mock is a stub, and once you use the proper mocks you might easily forget about the stub’s boilerplate at all.

[EDIT:] Repatch is a great tutorial of how to tweak Elixir macros, but it’s by no mean a proper mocking library. It simply guides you by hand to an abyss.

hauleth

hauleth OP

So for me the difference between mock and stub in Elixir is that mock is an implementation that replaces behaviour of some module, especially parts of this module.

So for example you use mock like that:

Repatch.mock(DateTime, :utc_now, fn -> ~U[2005-04-02 21:37:00+02:00] end)

list = Posts.select_recent()

Stub is when you write new implementation in your test, that will handle required functions and then that module is somehow injected into the function.

For example in my Aww library:

defmodule Dns do
  @behaviour AwwTest.Dns

  @impl true
  def handle_query("_avatars-sec._tcp.evil.test", :in, :srv) do
    %{
      ttl: 3600,
      data: {0, 0, 0xBEEF, ~c"defederated.example"}
    }
  end

  def handle_query("_avatars-sec._tcp.example.test", :in, :srv) do
    %{
      ttl: 3600,
      data: {0, 0, 0xBEEF, ~c"secure.example"}
    }
  end

  def handle_query("_avatars._tcp.example.test", :in, :srv) do
    %{
      ttl: 3600,
      data: {0, 0, 0xDEAD, ~c"insecure.example"}
    }
  end
end

setup do
  assert {:ok, pid, ns} = start_supervised({AwwTest.Dns, module: Dns})

  on_exit(fn ->
    Aww.Cache.clean(Aww.Cache)
  end)

  {:ok, pid: pid, ns: ns}
end

test "uses secure SRV data", %{ns: ns} do
  url =
    @subject.avatar_url("foo@example.test",
      service_opts: [
        host: :libravatar,
        resolv_opts: [nameservers: [ns]]
      ]
    )

  assert url.host == "secure.example"
  assert url.port == 0xBEEF
end

Where I create stub of DNS server that will then resolve the requested query when asked. I haven’t changed the behaviour of existing module, I have changed who is answering the questions.

Asd

Asd

What is the different between mocks, stubs and dependency injection? I’ve always thought that mock is a test-friendly implementation of some interface, while dependency injection is a technique which provides options to substitute the real implementation with a mock

Where Next? Top

Trending in Blog Posts Top

pckrishnadas88
Hey everyone! :waving_hand: I’ve published Part 7 of the Building Distributed Systems in Elixir series, where we build core distributed ...
New
mudasobwa
So, instead of wasting my afternoon arguing with anonymous handles on X, I turned to my trusty, soulless assistant and said: “Listen, ple...
New
zorn
An educational side project in Elixir, Phoenix, and Tauri. I share what I learned while wiring Automerge into the BEAM, including how I s...
New
abreujp
New article: Elixir Project Structure — From mix new to a Growing Codebase I’ve published a new article in my Elixir learning series on d...
New
zorn
:pencil: Phoenix’s built-in LiveView test helpers require you to hand-build the form payload and start a fresh pipe after every click. Wo...
New
jola
Wrote about how to safely run a globally unique process in an Elixir cluster, and a scary story from the past! Learn about :global for r...
New
nathanl
Process labels are useful for visualization and debugging. Here’s why you should use them.
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews