Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing captured IO from a spawned process

I want to test the return value and the IO output on the following method:

defmodule Speaker do
  def speak do
    receive do
      { :say, msg } ->
        IO.puts(msg)
        speak
      _other ->
        speak # throw away the message
    end
  end
end

In the ExUnit.CaptureIO docs, there is an example test that does this which looks like the following:

test "checking the return value and the IO output" do
  fun = fn ->
    assert Enum.each(["some", "example"], &(IO.puts &1)) == :ok
  end
  assert capture_io(fun) == "some\nexample\n"
end

Given that, I thought I could write the following test that performs a similar action but with a spawned process:

test ".speak with capture io" do
  pid = Kernel.spawn(Speaker, :speak, [])
  fun = fn ->
    assert send(pid, { :say, "Hello" }) == { :say, "Hello" }
  end
  assert capture_io(fun) == "Hello\n"
end

However, I get the following error message telling me there was no output, even though I can see output on the terminal:

1) test .speak with capture io (SpeakerTest)
   test/speaker_test.exs:25
   Assertion with == failed
   code: capture_io(fun) == "Hello\n"
   lhs:  ""
   rhs:  "Hello\n"
   stacktrace:
     test/speaker_test.exs:30: (test)

So, am I missing something perhaps with regards to testing spawned processes or methods that use the receive macro? How can I change my test to make it pass?

like image 654
Paul Fioravanti Avatar asked Jun 30 '26 02:06

Paul Fioravanti


1 Answers

CaptureIO might not be suited for what you're trying to do here. It runs a function and returns the captured output when that function returns. But your function never returns, so seems like this won't work. I came up with the following workaround:

test ".speak with capture io" do
  test_process = self()
  pid = spawn(fn ->
    Process.group_leader(self(), test_process)
    Speaker.speak
  end)

  send(pid, {:say, "Hello"})

  assert_receive {:io_request, _, _, {:put_chars, :unicode, "Hello\n"}}

  # Just to cleanup pid which dies upon not receiving a correct response
  # to the :io_request after a timeout
  Process.exit(pid, :kill)
end

It uses Process.group_leader to set the current process as the receiver of IO messages for the tested process and then asserts that these messages arrive.

like image 122
Paweł Obrok Avatar answered Jul 03 '26 16:07

Paweł Obrok



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!