OK here we go!
https://github.com/benwilson512/mox_demo/blob/main/lib/mox_demo.ex
This has your main function. As you can see I have swapped out the System IO and File to the general IO behavior module I define here. mox_demo/lib/io.ex at main · benwilson512/mox_demo · GitHub
As I note in that module, it’s a bit simplistic to put all the IO functions in one module. In the real world you’d probably define multiple behaviors, but I wanted to keep this simple.
Then we have the test:
https://github.com/benwilson512/mox_demo/blob/main/test/mox_demo_test.exs
As you can see I can define test specific expectations. In the end I didn’t even really need to manage any state, since the ability to chain expectations meant that when you call eg monotonic_time twice I just define two expectations, one with the first value, and one with the second.
I don’t have additional tests in there right now but these expectations are 100% isolated from any other tests that would be happening.
One nice consequence of this approach is that backend being parameterized you still get strong compiler and language server support since it resolves to a compile time value.
This is barely scratching the surface of what’s possible though and ironically, a lot of what’s possible is made possible by how minimalist Mox is. Here is the same test set up a different way where it’s more interactive. Basically instead of setting up all the IO ahead of time and calling main(), we put main in a task and then have it basically “talk to” our test process as if it is the IO world:
https://github.com/benwilson512/mox_demo/blob/main/test/interactive_test.exs
All I’m doing here is combining core Elixir primitives like send and receive with Mox. Mox doesn’t really have any state to track here other than knowing which functions to call for this particular invocation of main. Then the “state” of the IO is in this case managed by my test process itself by sending messages to the mock at the appropriate times.
And of course you’ll note that you can just run mix test in the project root directory and these two test files will run at the same time and do not interfere. Both approaches are perfectly valid. I tend to favor the Mox.expect approach when I just have some outside system that I need a value or two from, or that I want to assert we pushed a value to. I tend to use the second approach if I’m writing more of a “simulator” test where there is some IO heavy piece of code and I want to step through it. This particular example makes that look sort of verbose but that setup block is generic. Once you write your little stub you’re done, and each test just gets to really focus on the interaction between the function under testing and the IO.






















