How to inject module dependencies into Plug routers ?

233 views
Skip to first unread message

Emil Soman

unread,
Mar 31, 2016, 2:06:14 PM3/31/16
to elixir-lang-talk
I've been reading about the various schools of thought wrt testing Elixir code , ie, mocking vs dependency injection vs module swapping using configuration.
I'm personally in favor of injecting dependencies to functions. Now, I am not sure if core libraries like Plug etc have been built around this idea.

Let's say I have a router that looks like this :

```elixir
defmodule MyRouter do
  use Plug.Router
  plug :match
  plug :dispatch

  post "/attack" do
    # missile_launcher.launch_missile -> can the module "missile_launcher" be injected ?
    send_resp(conn, 200, "Missile launched")
  end

  match _ do
    send_resp(conn, 404, "oops")
  end
end
```

How do I inject a module into the router so that I can use it inside the actions (see the comment above).
This would make it easy to test this route using a mock module:

```elixir
defmodule MyRouterTest do
  use ExUnit.Case
  use Plug.Test

  # Can I use a fake MissileLauncher module when initializing the router ?
  @opts MyRouter.init([])

  test "asks Missile module to launch a missile" do
    conn = conn(:post, "/attack")

    conn = MyRouter.call(conn, @opts)

    assert conn.status == 200
    assert conn.resp_body == "Missile launched"
  end
end
```

Does the Plug.Router's init function allow us to pass in dependencies (or any custom options)
that can be accessed inside the route matchers ?

Cheers,
Emil

José Valim

unread,
Mar 31, 2016, 2:11:11 PM3/31/16
to elixir-l...@googlegroups.com
You can by using assigns. In your code:

conn.assigns[:missile_launcher] || Default

In your test, after you create the connection but before the call:

assign(conn, :missile_launcher, TestLauncher)



José Valim
Skype: jv.ptec
Founder and Director of R&D

--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/e1689bc7-4a7a-49c4-8b1f-1a36ebdb31ee%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Emil S

unread,
Mar 31, 2016, 2:38:13 PM3/31/16
to elixir-l...@googlegroups.com
Thanks José, this works well! By the way, is this the idiomatic way to test routers ? This is so much cleaner than setting up :meck expectations anyways :)

--
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-talk" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-talk/pdytVmrSAmI/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-ta...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/CAGnRm4Lczj87sJ56SrAJPtOYgf4bbD%3DZPAdTET0oLESk22q_fA%40mail.gmail.com.

Aaron Jensen

unread,
Apr 1, 2016, 10:37:52 AM4/1/16
to elixir-lang-talk
You can by using assigns. In your code:
conn.assigns[:missile_launcher] || Default

In your test, after you create the connection but before the call:
assign(conn, :missile_launcher, TestLauncher)

We've been using `put_private/3` instead, which puts things in `conn.private`. It felt like a better approach than overriding what `assigns` was used for. Is there anything wrong w/ that approach?

José Valim

unread,
Apr 1, 2016, 10:42:55 AM4/1/16
to elixir-l...@googlegroups.com
private is also fine. private was meant for libraries, since this is likely application code, it is fine for you to use assigns as well.



José Valim
Skype: jv.ptec
Founder and Director of R&D

--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages