aegatlin
How to find and kill an application-supervised, static (unnamed) Task in ExUnit (but only for some tests)?
I have an Agent and an infinite-running static Task that are application-supervised on startup. The Task is generating an array of numbers that are being stored in the Agent.
When testing, ExUnit starts these processes automatically, which I want for most of my tests (i.e., I don’t want to run mix test --no-start).
For both the Agent and the Task, I want to test their interactions. But, since they start automatically, by the time I can actually test them, they have been populated already with an arbitrary amount of data. It seems like a reasonable approach would be to kill both process and then recreate them with ExUnit.start_supervised. But, the static Task doesn’t have a name. So, the main question is: How do I find and kill an application-supervised static Task? (I don’t have this problem with my Agent because it is ‘named’, i.e., name: __MODULE__.
But, this doesn’t really solve my testing problem. Why? Because if I kill my application processes, the rest of my tests, which test functionality that depends on the Agent data being there, will all fail. What I really need is the processes to exist for the majority of my test suite, and then, somehow, I need to be able to isolate the tests of my Agent and Task from the greater application environment. (This presumably includes the complication of have the Agent have the same __MODULE__ name, so, maybe would involve a separate Registry? And, I’d prefer to not refactor my Agent module to receive different atom names just so I could test it, but if that’s necessary to get it work, I’d be happy to do it.)
I’ve been reading and experimenting all day, trying to find a way to get a passing test suite, but have failed so far. I can’t manage to find a way. Any feedback would be appreciated 
Most Liked
LostKobrakai
The biggest problem I feel people have with testing “global” processes, is that the fact that they’re hard coded to be global. Things get quite a bit simpler if naming a process (or registering elsewhere) becomes something controlled by start parameters, where an application started instance can still make sure its registered, but e.g. tests can easily start their own instances unnamed and therefore be more flexible in how to test the complete livecycle of the process.
LostKobrakai
Instead of testing the processes started by your application start another set of those processes for testing the interactions. Basically splitting up the parts of “the processes are started as part of the application” from the tests about “assert that the processes correctly interact with each other”.
aegatlin
Okay, well, this post seems to imply that long-running Tasks are almost an anti-pattern, and you should instead use a genserver: Best way to name permanent tasks? - #8 by ityonemo
The TL;DR as it applies to my issue involves using Process.register inside the Task module, so it’s named. And, also, to probably not use a Task, and instead use a GenServer.
I will attempt to rewrite my Task as a GenServer. It seems a bit like overkill, but maybe the added functionality potential will serve me well in the long run. What do you think @LostKobrakai ?
ityonemo
So, it seems like you are building some sort of “random number service” as the Agent. I guess, my question to you would be then why not just use a single GenServer and instead of using a task to generate your numbers, generate numbers in your GenServer between requests (you could probably also do this with Agent, but i kind of have a bias against Agent, in almost all cases you should not be using Agent). Do you really need the number generation to be asynchronous? Because once it becomes asynchronous, your complexity increases, you need to think about strange failure modes (what happens when the Task disappears but not the Agent? what about vice versa?), and if you have a “synchronous” genserver service doing the generation, then you only have to think about one line of execution and stuff stays (relatively) sane.
You could also build out a single instance of the server and test it in isolation.
Finally, if you are really adamant about having this Agent/Task architecture, I would put the Agent and the Task under a common supervisor, and start a “fresh supervisor” in every test you run. Don’t forget to hoist the “naming” option to an option in the supervisor setup, so that in your Application.start call it gets properly named, but when you spawn off new ones for testing, they’re not bound to a locally unique name
ityonemo
you might want to do something like :timer.send_interval(interval, self(), :tick) on init, Erlang -- timer if you need the generation to be triggered indepedently of other events happening to your GenServer and even if no other events happen.
My personal preference is that I don’t like Process.send_after(self(), :tick, interval), because it feels like you if you are not careful coding, you could accidentally retrigger it n times and wind up with n times more events than you wanted.
If you only need it in a responsive sense (only need to update the list after some event has happened), I recommend using the handle_continue, I think if you can get away with doing that that is the best way, because your event schedule is more “deterministic” for testing purposes, and probably it’s easier to reason about.







