fredmcgroarty

fredmcgroarty

"(EXIT) already started:" when testing parts of application

I am trying to write a test that covers the behavior more or less of the entire application - technically these are more integrative tests than strictly unit tests - and I feel as though there is something missing in my understanding of how processes are started and named that is stopping me from being able to test the application the way I want.

How do I instruct the application code of the names assigned to the associated processes it calls on during the run of a test? I have seen similar questions relating to this issue/error but I am too inexperienced to see the answer for my particular context.

I have the following Supervisor, which starts 3 custom written workers and another from this library

defmodule Krown.Supervisor do          
  use Supervisor                       
  # code omitted for brevity  
                                                                                                                                                                                                 
  def start_link(init_arg \\ []) do                                   
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)     
  end                                                                 

  def init(args \\ []) do                                                                            
                                                                                                   
    children = [                                                                                     
      {FileSystem.Worker,                                                                            
       [                                                                                             
         name: Keyword.get(args, :file_event_name, FileEventNotify),                                 
         recursive: true,                                                                            
         dirs: [Application.fetch_env!(:my_app, :event_dir)]                                          
       ]},                                                                                           
      {Dispatcher, [name: Keyword.get(args, :dispatcher_name, Dispatcher)]},  
      {Converter, [name: Keyword.get(args, :converter_name, Converter)]},
      {Uploader, [name: Keyword.get(args, :uploader_name, Uploader)]},           
    ]                                                                                                
                                                                                                   
    Supervisor.init(children, strategy: :one_for_one)                                                
  end                                                                                                
end

The Dispatcher, which monitors messages in FileSystem.Worker, looks like this:

defmodule Krown.Dispatcher do
  use GenServer                                                                                                                     
  # code omitted for brevity                                                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                             
  def init(_) do                                                                                                                    
    FileEventNotify                                                                                                                 
    |> Process.whereis()                                                                                                            
    |> FileSystem.subscribe()                                                                                                       
                                                                                                                                    
    {:ok, nil}                                                                                                                      
  end                                                                                                                               
                                                                                                                                    
  def handle_info({:file_event, _watcher_pid, {fpath, events}}, _state) do                                                                                                                                                                                                                                                                                                                                                                                                                                                          
    Path.dirname(fpath)                                                                                                             
    |> Path.split()                                                                                                                 
    |> List.last()                                                                                                                  
    |> case do                                                                                                                      
      "raw" ->                                                                                                           
        Converter                                                                                                                     
      "wav" ->                                                                                                         
        Uploader                                                                                                      
     end                                                                                                                             
     |> case Process.whereis() do                                                                                             
        nil ->                                                                                                                      
          Logger.warn("Unable to locate pid with name: #{pid_name}")                                                                
          raise "#{pid_name} PID NOT FOUND"                                                                                                                                                                                                                
        pid ->                                                                                                                      
          GenServer.cast(pid, {:perform, fpath})                                                                                    
      end                                                                                                                           
                                                                                                                                    
    {:noreply, nil}                                                                                                                 
  end                                                                                                                               

And a basic test for illustrative purposes:


  test "something contrived "                           
    spec = %{                                                                         
      id: __MODULE__,                                                                    
      start: {                                                                           
        DescribedModule,                                                                 
        :start_link,                                                                     
        [[uploader_name: Test.Uploader,                                                  
          converter_name: Test.Converter,                                                
          file_event_name: Test.FileEventNotify,                                         
          event_dispatcher_name: Test.Dispatcher]]                                       
      }                                                                                  
    }                                                                                    
    pid = start_supervised!(spec, restart: :temporary)                                
             
    # create a file that will trigger FileEventNotify and Dispatcher 
    File.touch("some_fpath")                                                                            
    assert Process.alive?(pid)     

   # Uploader will remove the file once it has completed #perform
    refute File.exists?("some_fpath")
                                                                                                                      
  end                                                                                    

Declaring names for each GenServer in the test avoids the ** (EXIT) already started: #PID<0.284.0> type error, but it effectively breaks my application in the Dispatcher, since the dispatcher relies on there being static names for the two processes it sends messages to in #perform/2.

So I think to myself, “maybe I am missing something, possibly the use of a Registry?”. But I played with that and it took me back to my first problem whereby the application code has no way of knowing what the dynamically named Registry is.

I noticed that if I comment out mod: {Krown, []} from mix.exs, then I could run the tests successfully - but obviously this is not something I can use successfully in the real world, so what’s the point?

Maybe I am thinking about this all wrong. I feel I am stuck in my understanding. I guess I am writing more integration tests vs unit tests? If so, should I structure them differently?
I also have doubts the vocabulary I am using to express the issue I am having, so any feedback on that is also welcome.

First Post!

eksperimental

eksperimental

I’m not 100% this is what you may need, but you can pattern match on the context of the test.

test "something contrived ", %{test: test_name} = _context do
  # test_name is a unique string that you can use to identify your GenServers.
end

you can also use setup/2 to create a genserver automatically, and pass it through the context, but in this case you will have to randomly generate the name as the test name won’t be available.

Where Next?

Popular in Questions Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
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
mathew4509
I have a list say x = ["23gh", "56kh", "97mh"] I would like to pass each element to Val in each iteration. Say, in iteration 1 -------...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
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
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
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
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

We're in Beta

About us Mission Statement