marick

marick

Decoupling client code and tests from complex structures

I have written a description of one way to allow complex structures to change without breaking a lot of client code and tests. It describes the use of one package you can add to Mix dependencies, plus two modules you can copy and tweak. I show the beginning of the writeup below. The whole thing is at Stubbing Complex Structures. Around 300 lines, including code samples.


Sometimes, despite what you might want, you end up with a complex structure – sometimes called a “God object” – that’s used in many places in your code. If that client code contains text like user.privileges[:author].read, you have two problems:

  1. Any attempt to change the “shape” of the complex structure becomes hard because there are so many places to change. That locks you into complexity because it’s too painful to undo it by, for example, breaking the single structure into several.

  2. Tests have to construct sample data. In a dynamically typed language, they don’t have to create a complete God object; they need only supply the fields the code under test actually uses. But, once again, changes to the structure can require a lot of changes to tests.

Here, I’ll show how to avoid such coupling, using the code in MockeryExtras.Getters and MockeryExtras.Given. The emphasis is on both simplifying change and avoiding busywork.

Look in the example directory for working code to adapt.

TL;DR

Make getters

defmodule Example.RunningExample do
  import MockeryExtras.Getters
  
  defstruct [:example, :history]

  getters :example, [
    eens: [], field_checks: %{}
  ]
  getters :example, :metadata, [
    :name, :workflow_name, :repo, :module_under_test
  ]

Use getters - tersely and stubbably - in client code:

defmodule Example.Steps do
  use Example.From

  def assert_valid_changeset(running, which_changeset) do 
    from(running, use: [:name, :workflow_name])                 # <<<<
    from_history(running, changeset: which_changeset)           # <<<<
    # `name = RunningExample.name(running)`, etc. is too wordy

    do_something_with(name, workflow_name, changeset)
  end

The above requires copying and slightly tweaking code in Example.From.

Don’t build structures, stub getters

defmodule Example.StepsTest do
  ... 
  import Example.RunningStubs

  setup do # overridable defaults
    stub [name: :example, workflow_name: :success]
    :ok
  end

  test "..." do 
    stub(field_checks: %{name: "Bossie"}, ...)
    stub_history(inserted_value: ...)
    ...
    assert ...
end

The above requires copying and slightly tweaking code in Example.RunningStubs.

Where Next?

Popular in Guides/Tuts Top

bitli
In case this is handy for other people, here is how you can run Elixir on Android: Install https://termux.com/ apt update; apt upgrade ...
New
dgamidov
Hi folks, Just a short instruction. Maybe it will help somebody. Install boostrap with deps and Sass: cd assets npm install jquery ...
New
WolfDan
So my main OS is Windows, I do must of my work with it, Elixir and vscode elixirls works just fine when you’re working only with elixir, ...
New
zenw0lf
Hello all! For those wanting to try your hands at Elixir / Phoenix, I wrote a comprehensive tutorial on doing a simple JSON API with sai...
New
nietaki
Just a quick heads up: There seems to be a bug in Erlang/OTP 21.3, which can cause some errors when making http requests. If you’re using...
New
dkuku
I Created a blog post about setting up svelte with phoenix. I found it a bit tricky and the only blog post I found was written using som...
New
sergio
Wrote this guide on how to integrate DropzoneJS with Phoenix Liveview. Hope it helps someone out! Woah read that title again, what a ...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 131117 1222
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

We're in Beta

About us Mission Statement