elixir-lang / elixir

Elixir is a dynamic, functional language for building scalable and maintainable applications
https://elixir-lang.org/
Apache License 2.0
24.34k stars 3.36k forks source link

ArgumentError by `@enforce_keys` is not catched by `assert_raise` #6597

Closed schaary closed 7 years ago

schaary commented 7 years ago

Environment

Elixir 1.5.1


* Operating system: macOS 10.12.5

### Current behavior

I define a struct in a module and I want to enfoce that certain keys have to be specified. So I have a `@enforce_keys` statement:

```elixir
defmodule Foo do

  @enforce_keys [:id, :name]
  defstruct :id, :name, :foo, :bar

end

The test looks like this:

defmodule FooTest do
  use ExUnit.Case

  test "the exception" do
    assert_raised ArgumentError, fn -> 
      %Foo{}
    end
  end

end
$> mix test --trace
** (ArgumentError) the following keys must also be given when building struct Foo: [:id, :name]
    (MyProject) expanding struct: Foo.__struct__/1
    test/foo_test.exs:9: FooTest."test the exception"/1
    (elixir) lib/code.ex:376: Code.require_file/2
    (elixir) lib/kernel/parallel_require.ex:59: anonymous fn/2 in Kernel.ParallelRequire.spawn_requires/5

Expected behavior

I would expect the test runs successfully. When I try the example from https://hexdocs.pm/ex_unit/ExUnit.Assertions.html#assert_raise/2 the test runs successfully. But with @enforce_keys and a test of ArgumentError not.

josevalim commented 7 years ago

All struct errors happen at compile time so indeed they won't be caught at runtime. To put it in other words, the module FooTest does not successfully compile.

marcdel commented 6 years ago

Hi @josevalim, I was wondering if you have opinions about using something like "initializer methods" for this?

I was test driving a similar bit of code and added a new/2 method as a documented way (I think I added a doctest instead in my case) to create a valid %Foo{}.

defmodule FooTest do
  use ExUnit.Case

  test "new/2 creates a valid Foo" do
    %Foo{} = Foo.new(1, "name")
  end
end
defmodule Foo do
  @enforce_keys [:id, :name]
  defstruct :id, :name, :foo, :bar

  def new(id, name) do
    %Foo{id: id, name: name}
  end
end

I was also considering doing something like https://thepugautomatic.com/2015/09/testing-compile-time-exceptions-in-elixir/ to test the enforce_keys explicitly as well, but that seems like overkill in this case?