fredmcgroarty

fredmcgroarty

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.

Showing Posts 1 to 2

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.

eksperimental

eksperimental

Also creating a macro that will return the file path and line number works.

defmacrop id() do
  quote do
    {__ENV__.file, __ENV__.line}
   end
end

#and then in your spec
id: id()

or you can call {__ENV__.file, __ENV__.line} directly.

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews