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

OndrejValenta
Me and my boys started a new website specifically designed for other ASP.NET programmers that struggle, as we do, with their transformati...
New
hlx
Typed my very first blog post ever, hope it will help some people https://henricus.xyz/roll-your-own-email-password-authentication-with...
New
sergio
I couldn’t find any guides that worked well with Phoenix 1.6.0 and esbuild. I hope this helps people test the waters and eases you into t...
New
bentanweihao
I wrote a thing: http://engineering.pivotal.io/post/how-to-set-up-an-elixir-cluster-on-amazon-ec2/ Hope this is helpful!
New
jshprentz
Geoffrey Lessel’s 2019 book, Phoenix in Action, was written for Phoenix 1.4. I found that the book’s code examples did not match the cur...
New
njwest
In the process of developing a Phx-based multiplayer experience, I found myself with so many browser tabs open with Elixir gaming resourc...
New
dmitriid
This is not a question, but a post for anyone who may need it in the future. Sometimes some browsers may mangle the filename if it’s non-...
New
thetechnologyvault
One of our team members just published this getting started guide for Elixir/Phoenix devs to use the Nanobox platform: https://content.n...
New
georgeguimaraes
Another cool plugin for Neovim, GitHub - jmbuhr/otter.nvim: Just ask an otter! 🦦 makes it possible to run linters for embedded code, like...
New
mudasobwa
The post covering how to generate nifty types to use in @spec in compile time with macros. https://rocket-science.ru/hacking/2020/07/15/...
New

Other popular topics Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

We're in Beta

About us Mission Statement