Red Green Repeat Adventures of a Spec Driven Junkie

Mocking Elixir IO.puts

I’m diving head first into Elixir, but not following a linear approach. I’m taking the best experience I have from other programming languages and diving into parts of elixir I need. (So, I will be skipping lots of language details.)

Last post, I got into testing and building executables. The minimum for building out confident code for a client.

This time, let’s get into mocking. Not just any mocking, but mocking out user input and output! Software may interact with a person… and simulating a person through mocking will reduce development time.

Mocking in ExUnit

I love RSpec’s mocking faciities and with the great experience I had with ExUnit, I expected fantastic mocking.

At the time of writing, there wasn’t any mocking built-in to ExUnit. :-/ Shame, the relationship started so well!

I found this mock project by jjh42 and it’s fulfilling my mocking needs.

Mocking other functions is easy, but mocking input and output… even RSpec had a hard time with that! Let’s see how to mock out user input and output with elixir.

IO.puts

The main Elixir method to print to the console or to debug is using IO.puts. This is the equivalent of Ruby’s puts or Python’s print, C’s printf. Where would programming be without this fundmental feedback tool??

The README explains how to mock IO.puts:

defmodule MyTest do
  use ExUnit.Case, async: false
  import Mock

  test_with_mock "test_name", IO, [:passthrough], [] do
    IO.puts "hello"
    assert called IO.puts "hello"
  end
end

Simple right?

Well, when I started to run tests, I started to see:

vagrant@vagrant:/vagrant/hello$ mix test
Compiling 1 file (.ex)
..hello
.

Finished in 0.2 seconds
3 tests, 0 failures

Randomized with seed 812063
vagrant@vagrant:/vagrant/hello$

Huh? I thought I was mocking out the function. Why is it printing to the screen?

:passthrough

Looks like the :passthrough option is like RSpec’s and_call_original, which is useful when wanting to use the original method in the test.

In this case though, not printing to console would be desirable. I love to have continuous green dots.

To elimiinate output, remove :passthrough as an option to mock:

  test_with_mock "test_name", IO, [], [] do
    IO.puts "hello"
    assert called IO.puts "hello"
  end

results

vagrant@vagrant:/vagrant/hello$ mix test


  1) test test_name (HelloTest)
     test/hello_test.exs:13
     ** (UndefinedFunctionError) function IO.puts/1 is undefined or private. Did you mean one of:

           * puts/1
           * puts/2

     code: IO.puts "hello"
     stacktrace:
       (elixir) IO.puts("hello")
       test/hello_test.exs:14: (test)

..

Finished in 0.2 seconds
3 tests, 1 failure

Randomized with seed 319412
vagrant@vagrant:/vagrant/hello$

Weird… so the :passthrough option was not causing the outpu. So, how I do mock out IO.puts without on screen output?

Mocking Functions

Looking at other examples on the README, there are functions associated like so:

 test "test_name" do
    with_mock HTTPotion, [get: fn(_url) -> "<html></html>" end] do
      HTTPotion.get("http://example.com")
      # Tests that make the expected call
      assert called HTTPotion.get("http://example.com")
    end
  end

Let’s try using a simple function to IO.puts:

  test_with_mock "test_name", IO, [], [puts: fn(_) -> nil end] do
    IO.puts "hello"
    assert called IO.puts "hello"
  end

Running everything again:

vagrant@vagrant:/vagrant/hello$ mix test
...

Finished in 0.2 seconds
3 tests, 0 failures

Randomized with seed 356012
vagrant@vagrant:/vagrant/hello$

Nice, nothing extra on the output line. This took a little more work, but it makes sense.

Conclusion

This is a quick tour of mocking in elixir using jjh42’s mocking library. Getting IO.puts mocked out so there is no on-screen output during test runs.

This time, I covered IO.puts. Next time, I will go in-depth with IO.gets and also add some functionality.