Testing GenServer handling ETS records

I have a GenServer started under supervision tree inside application.ex. When I run mix test it is being started correctly. But this prevents testing the module itself in isolation, like “what happens upon init()” for example. Moreover as it manages some records in ETS, stuff remains in those between tests. So I’d like it to have a clean slate for every test. Seems easy but none of the “solutions” that come to mind

  • making the start in application.ex conditional, depending on the mix environment, and then start the server myself in test modules’ setup with something like start_supervised!/2
  • terminating and (somehow) waiting for the server to be restarted by the supervisor
  • […]

look to me different than half-witted and scruffy-looking :wink: Anything more correct/elegant/smarter up in your experts’ sleeves?

What’s the problem with start_supervised?

Technically it works but my gut feelings make me wonder if putting things like if Mix.env == :test [...] into application.ex is the most “correct” way of tackling the issue. What if at some point Mix.env is not available in production? I’d have to start putting more conditionals like checking whether Mix.env is in fact available, and that starts smelling even more, doesn’t it? Or maybe not - that’s the question.

this won’t work in releases since :mix (and other build tools) are not available there.

You can use app env instead of mix. Like config :my_app, enable_some_genserver: config_env() != :test and checking if Application.get_env(:my_app, :enable_some_genserver) do ... end

1 Like

And how does this (not commented) line from “standard” Phoenix config.exs work without Mix?

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"

It’s executed during compilation (config/<env>.exs are “compile-time” configs), however using Mix.env in config/runtime.exs would fail.

2 Likes

It’s best to use the config/*.exs files indeed. And then you can have a helper module with functions returning values which are determined by these config options (that part is optional though, it’s just for syntactic sugar). Works really well for me and my projects.

It seems almost obvious… now, when you guys mentioned it… And my guts don’t object either :wink: Thank you @ruslandoga, @dimitarvp

You can also do mix test --no-start and then just selectively start anything as necessary as it will prevent any processes in your application from starting.

I use this to test certain parts of my application in isolation when in production they get started automatically.

3 Likes

Do you set the :name of your server in the child spec or in the module code?

Currently in the child spec. Why?

Then you can already test your process in isolation since it does not manage its own name. Isn’t that me most correct way of testing?

Except if you use a public named table, in that case you may directly call the init, handle_* functions from the test.

Do you mean that I can just start another “MyGenServerTest” in the tests? Well, not really…

… because yes, the server creates a few public, named ETS tables upon start.

Right, but since another instance is already running, the preparatory init work, which I want to test “before” and “after” has already been done. For example the tables are already there, then they’re already holding some records, externals have already been set, etc. So unless I misunderstood your intentions, making it testable this way would require to parameterise a number of things in the server. Doable, yet not sure if it’s worth in this particular case.

That’s interesting too! OTOH that would require starting “manually” most things in the majority of other tests. Anyway - interesting option to know about.

“testable in isolation” and “singleton design” are things, which don’t work well together. I’d suggest building components, which can be configured to work like a singleton, but are not hardcoded to be.

2 Likes

Hehe… I was expecting something like this when I saw the indicator about you responding :slight_smile: Don’t get me wrong here, your input is both valuable and highly appreciated. For this particular case I’ll for the moment stay with what I have but shall surely keep your point in mind if I end up rewriting it due to lack of enough flexibility.

can’t you just pass a prefix to your server for the table names? Then in your test you use another prefix and do not care about the existing singleton.

As I mentioned, parameterising it would help and if I run into problems with current solution that will be the next thing to do.

1 Like