[Proposal] introduce with_env in ExUnit

78 views
Skip to first unread message

Daniel Kukula

unread,
Mar 27, 2024, 11:14:18 AMMar 27
to elixir-lang-core
Currently, tests that use env variables can't be run async because the environment is shared. My proposal is to introduce a with_env function that will accept a function to execute and a mapping of params that the functions in System will accept as env variables:
Now: System.put_env("PORT", "4000")
assert some logic
System.delete_env("PORT")

after

with_env(%{"PORT" => "4000}, fn -> 
assert some logic
end)

José Valim

unread,
Mar 27, 2024, 5:26:51 PMMar 27
to elixir-l...@googlegroups.com
I don't see a strong need to make this part of ExUnit, especially because:

1. We should avoid mutating the global state in tests
2. We should avoid reading system environment variables in code (use config/runtime.exs instead)

Those can add to their own suites if necessary. :)

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/3e7dcf24-ae69-47b0-a7b5-281055c70726n%40googlegroups.com.
Message has been deleted

Andrea Leopardi

unread,
Mar 29, 2024, 12:17:43 PMMar 29
to elixir-l...@googlegroups.com
You could experiment with a library that does this:
- Provides alternatives for System.get_env/2 and friends to use in their place
- Uses https://github.com/dashbitco/nimble_ownership to set and retrieve env in tests, to allow for asynchronous tests

However, system env is still global state. At the end of the day, a better/simpler approach is to probably do something like provide stubs for reading the env value and using Mox or something like that in tests.

Andrea

Paul Schoenfelder

unread,
Mar 29, 2024, 12:17:44 PMMar 29
to elixir-l...@googlegroups.com
Ideally, your system pushes this kind of "global" configuration down from the top of the supervision tree, rather than reading global state like this in a bunch of places. I would definitely encourage you to try and build your system with that in mind. Then, all of your tests can pass specific configuration to the part of the system under test, rather than relying on global state.

But as an alternative approach for your use case: you could implement a simple wrapper function around `System.get_env` (etc.) that reads/writes to a special key in the process dictionary, falling through to calling the `System` APIs if the process dictionary doesn't have the `:env` key (or whatever you want to call it).

Then, all you need to do in your tests, is store the env vars you want in the process dictionary of the test, and those will override the global `System` values.

Things get a bit trickier when spawning processes is involved, but there are various approaches for this (I believe ExUnit abuses these pretty heavily itself IIRC). One of them is to look at the ancestors of the current process until you either reach the root process, or you find one with the specified key in the process dictionary.

The reason why implementing this in ExUnit itself is likely to see pushback, is because there are just so many ways for this kind of global state to find its way into a program, and ExUnit cannot handle them all. Furthermore, I believe José wants to encourage good system design by making it harder to do things in a not-recommended way, i.e. in this case, not relying on global configuration deep inside your system. As a practical matter, it would also require adding the sort of wrapper code I mentioned to `System.get_env`, which isn't particularly palatable I would imagine.

Anyway, hope that helps/gives you some ideas!

Paul

Daniel Kukula

unread,
Mar 30, 2024, 4:30:54 PMMar 30
to elixir-lang-core
Using mox may be the way to go. Thanks for all your suggestions.

Jason Axelson

unread,
Apr 2, 2024, 4:32:41 PMApr 2
to elixir-l...@googlegroups.com
> But as an alternative approach for your use case: you could implement a simple wrapper function around `System.get_env` (etc.) that reads/writes to a special key in the process dictionary, falling through to calling the `System` APIs if the process dictionary doesn't have the `:env` key (or whatever you want to call it).
>
> Then, all you need to do in your tests, is store the env vars you want in the process dictionary of the test, and those will override the global `System` values.

FYI there's a library that does basically exactly this. Maybe it's worth checking out for anyone interested in this approach https://github.com/jbsf2/process-tree

-Jason

Reply all
Reply to author
Forward
0 new messages