How to test a compile error?

Is there a good way to test an exception is raised in a __before_compile__?

For example

defmodule MustBeValid do
  defmacro __using__(is_valid?) do
    quote do
      @is_valid unquote(is_valid?)
      @before_compile unquote(__MODULE__)
    end
  end

  defmacro __before_compile__(env) do
    unless Module.get_attribute(env.module, :is_valid) do
      raise "invalid"
    end
  end
end

defmodule WillFail do
  use MustBeValid, false
end

You can eval/compile files at runtime. Just build a fixture and test against it.
You should change raise "invalid" to something a bit more helpful tho. maybe:
raise CompileError, description: "missing attribute", file: __ENV__.file, line: __ENV__.line

defmodule MustBeValidTest do
   @fixture_file Path.join(["fixtures", "must_be_valid.ex"]
   @external_resource @fixture_file

   use ExUnit.Case

   test "CompileError" do
      assert_raise CompileError, "Some Helpful Info", fn() -> 
         Code.eval_file(@fixture_file)
      end
   end
end
3 Likes

Code.eval_quoted also works quite well:

test "CompileError" do
  assert_raise CompileError, "Some Helpful Info", fn() ->
    ast = quote do
      defmodule WillFail do
         use MustBeValid, false
      end
    end
    Code.eval_quoted(ast, []. __ENV__)
  end
end
5 Likes

I like this much better than my solution think.

Thanks both of you!

Since the error comes from inside a module, I don’t even think you need eval:

test "CompileError" do
  assert_raise CompileError, "Some Helpful Info", fn() ->
    defmodule WillFail do
      use MustBeValid, false
    end
  end
end
4 Likes

Is there a situation where @michalmuskala’s solution is required?

If you want to test a regular macro and you simply invoke it inside the test, then the error will come when the test is expanded, before the tests even run, which is not what you want. Then you have to either put the macro inside a module, as I suggested, or use eval, as @michalmuskala suggested.

4 Likes