Programming Phoenix 1.4 - Testing Channels and OTP

We have the following test at the beginning of the chapter

​defmodule​ InfoSysTest.CacheTest ​do​
​ use​ ExUnit.Case, ​async:​ true
​ alias InfoSys.Cache
​ @moduletag ​clear_interval:​ 100
​ 	
​ setup %{​test:​ name, ​clear_interval:​ clear_interval} ​do​
​   {​:ok​, pid} = Cache.start_link(​name:​ name, ​clear_interval:​ clear_interval)
​   {​:ok​, ​name:​ name, ​pid:​ pid}
​ ​end​

Where is name supposed to be coming from? This obviously makes the test fail

It is the test context. Here is the relevant document

Hey thanks for the link. I just read the section and couldn’t find an answer

The chapter adds the following tests:

test ​"​​key value pairs can be put and fetched from cache"​, %{​name:​ name} ​do​
​  assert ​:ok​ = Cache.put(name, ​:key1​, ​:value1​)
​  assert ​:ok​ = Cache.put(name, ​:key2​, ​:value2​)
​ 	
​  assert Cache.fetch(name, ​:key1​) == {​:ok​, ​:value1​}
​  assert Cache.fetch(name, ​:key2​) == {​:ok​, ​:value2​}
​end


test ​"​​key value pairs can be put and fetched from cache"​, %{​name:​ name} ​do​
​  assert ​:ok​ = Cache.put(name, ​:key1​, ​:value1​)
​  assert ​:ok​ = Cache.put(name, ​:key2​, ​:value2​)
​ 	
​  assert Cache.fetch(name, ​:key1​) == {​:ok​, ​:value1​}
​  assert Cache.fetch(name, ​:key2​) == {​:ok​, ​:value2​}
​end​
​ 	
​test ​"​​unfound entry returns error"​, %{​name:​ name} ​do​
​  assert Cache.fetch(name, ​:notexists​) == ​:error​
​​end​

Which fails with:

warning: variable "name" does not exist and is being expanded to "name()", please use parentheses to remove the ambiguity or change the variable name
  test/cache_test.exs:12: InfoSysTest.CacheTest."test key value pairs can be put and fetched from cache"/1

warning: variable "name" does not exist and is being expanded to "name()", please use parentheses to remove the ambiguity or change the variable name
  test/cache_test.exs:13: InfoSysTest.CacheTest."test key value pairs can be put and fetched from cache"/1

warning: variable "name" does not exist and is being expanded to "name()", please use parentheses to remove the ambiguity or change the variable name
  test/cache_test.exs:15: InfoSysTest.CacheTest."test key value pairs can be put and fetched from cache"/1

warning: variable "name" does not exist and is being expanded to "name()", please use parentheses to remove the ambiguity or change the variable name
  test/cache_test.exs:16: InfoSysTest.CacheTest."test key value pairs can be put and fetched from cache"/1

warning: undefined function name/0
  test/cache_test.exs:16

warning: undefined function name/0
  test/cache_test.exs:15

warning: undefined function name/0
  test/cache_test.exs:13


== Compilation error in file test/cache_test.exs ==
** (CompileError) test/cache_test.exs:12: undefined function name/0
    (elixir 1.11.3) src/elixir_locals.erl:114: anonymous fn/3 in :elixir_locals.ensure_no_undefined_local/3
    (stdlib 3.14) erl_eval.erl:680: :erl_eval.do_apply/6
    (elixir 1.11.3) lib/kernel/parallel_compiler.ex:416: Kernel.ParallelCompiler.require_file/2
    (elixir 1.11.3) lib/kernel/parallel_compiler.ex:316: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7

Any ideas ?

%{test: name}, not %{name: name}
Please read this doc

Sorry, it looks like the setup was trying to merge in [name: name, pid: pid] to the context, so that should work. did you add a differrent setup clause so the setup you cited at the beginning did not run?

Silly me forgot to add %{name: name} in one of the test headers

However I did just paste the whole thing and now am getting following errors:

  1) test unfound entry returns error (InfoSysTest.CacheTest)
     test/cache_test.exs:36
     ** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.178.0>}}
     stacktrace:
       test/cache_test.exs:24: InfoSysTest.CacheTest.__ex_unit_setup_0/1
       test/cache_test.exs:1: InfoSysTest.CacheTest.__ex_unit__/2



  2) test key value pairs can be put and fetched from cache (InfoSysTest.CacheTest)
     test/cache_test.exs:28
     ** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.178.0>}}
     stacktrace:
       test/cache_test.exs:24: InfoSysTest.CacheTest.__ex_unit_setup_0/1
       test/cache_test.exs:1: InfoSysTest.CacheTest.__ex_unit__/2



Finished in 0.06 seconds
2 tests, 2 failures

Randomized with seed 790950

It is a good exercise, please try to figure it out on your own. You can do it.

2 Likes

Thanks for the encouragement :slight_smile: and sorry to disappoint but I’ve been trying since and haven’t succeeded. Here is my thought process so far

If the process is already started, then something must be starting it twice with the same name.

So I stick a simple debug line in the start_link method to see how many times it gets called, and sure enough it only gets called once.

I figure maybe it just wasn’t properly exited from a previous test, but then again iex isn’t running nor are the tests and if I’m not mistaken in both cases the GenServer would be linked to the calling process (either iex or test context), and thus would be exited.

So the issue is most likely in the test definition itself, however start_link definitely only happens once, since the IO.puts I inserted in it only prints once

Gonna keep at it but not sure where to go from here

I also still do not understand where the name in setup is coming from. I get that setup runs before every test and that if there are tags above said tests that they will be injected into setup, but this is not the case

name in the setup is coming from the matched :test key, which is put into the context by exUnit by default to the name of the test.

setup is ran per test. Do you have 2 tests by the same name? Do you know if the Cache module pass the name down to registration? Processes shall be registered using atoms not strings. So I have doubt.

1 Like

This was it. I had:

  def start_link(opts) do
    opts = Keyword.put_new(opts, :name, __MODULE__)
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

I miscopied, in the book is written:

  def start_link(opts) do
    opts = Keyword.put_new(opts, :name, __MODULE__)
    GenServer.start_link(__MODULE__, opts, name: opts[:name])
  end

The tests now pass, but this leads to me another question:

How is opts[:name] in this case not always the same, i.e., opts[:name] == __MODULE__?

So I logged opts[:name] as follows:

  def start_link(opts) do
    opts = Keyword.put_new(opts, :name, __MODULE__)
    IO.inspect(opts, label: "OPTS")
    GenServer.start_link(__MODULE__, opts, name: opts[:name])
  end

And I get:

OPTS: [name: InfoSys.Cache]
OPTS: [
name: :“test key value pairs can be put and fetched from cache”,
clear_interval: 100
]
.OPTS: [name: :“test unfound entry returns error”, clear_interval: 100]

I thought __MODULE__ was always the name of the module in which it’s written. Does this mean that the value of __MODULE__ is actually the value of module calling it?

Keyword.put_new/3 will only put when the key was not there already. In your case, the opts is coming from the setup, which put in a :name key already.

I learned something too; it looks like you can register a process under a string, instead of the usual atom. I still don’t think this is a good practice though.

1 Like

Makes sense.

OPTS: [
name: :“test key value pairs can be put and fetched from cache”,
clear_interval: 100
]
.OPTS: [name: :“test unfound entry returns error”, clear_interval: 100]

pretty sure these are still atoms, no?

You are right, it is still atom, just long and weird looking.