elixir-lang / elixir

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

Allow matching on ExUnit setup/setup_all blocks #4451

Closed josevalim closed 8 years ago

josevalim commented 8 years ago

For example:

setup %{login_as: username} do
  # ...
end

will only be invoked if the context as the given key. We should also support guard clauses:

setup %{login_as: username} when is_binary(username) do
  # ...
end

/cc @chrismccord

danhper commented 8 years ago

I would like to give this one a try if nobody is on it yet!

lexmag commented 8 years ago

Please go ahead.

ericmj commented 8 years ago

This could be confusing since multiple setups does not generate clauses, instead they just create new functions. Notice the difference in behaviour between functions and setups:

def foo(%{login_as: username}), do: ...
def foo(context), do: ...

setup %{login_as: username}, do: ...
setup context, do: ...

For the function only one clause will run if the argument matches %{login_as: ...}, for the setup both blocks will run.

josevalim commented 8 years ago

I thought about it but I thought it was still worth it because today there is a lot of code like this:

setup context do
  if username = context[:login_as] do
    ...
  else
    :ok
  end
end

Maybe we could have something like setup_matching but I am not convinced. Thoughtss?

josevalim commented 8 years ago

Btw, the reason why it may not be a problem is because we know that

setup context do
setup context do

will run both setup blocks.

lexmag commented 8 years ago

Hmm, as for me, today's if way feels good enough. I have nightmares about deeply nested before-callback hell in RSpec where global state mutates. :bowtie:

Thankfully we don't do nesting, thus in my rating if >= setup > setup_matching.

josevalim commented 8 years ago

I hate the if way because we have about 4 lines of boilerplate compared to today's solution. Sometimes it is more boilerplate than actual code.

michalmuskala commented 8 years ago

You could use with to simplify:

setup context do
  with {:ok, %{login_as: username}} <- {:ok, context},
   do: {:ok, login(context)}
end

Maybe we could have something to reduce this {:ok, value} wrapping, like:

setup with %{login_as: username} <- context,
       do: login(context)
josevalim commented 8 years ago

I find the with proposal quite hard to read, to be honest. :(

whatyouhide commented 8 years ago

I'm leaning towards a :-1: on the with proposal as I find it hard to read as well. I'm inclined to agree with @lexmag here, a simple if may be more verbose but it's clear and people know what to expect from it. If we go with the proposed solution, we introduce some kind of complexity (for the reasons @ericmj mentioned as well) that we will have to document (intensively :smile:) for people to be confident with.

danhper commented 8 years ago

We can already write

setup_all do
  {:ok, %{foo: "bar"}}
end

setup do
  IO.puts("this will run anyway")
  :ok
end

setup %{foo: v} do
  IO.puts("foo = #{v} but I die if foo is not here")
  :ok
end

so the behavior of running all the clauses already exists. Therefore, I do not think that it would really increase the complexity or make things harder to understand if we skip the clause instead of just failing with a FunctionClauseError.

danhper commented 8 years ago

I just opened a PR, so maybe we can continue the discussion there?

josevalim commented 8 years ago

My counter-proposal is to add a new set of macros: setup_if, setup_all_if and test_if that will only run if the context matches. There may be better options than setup_if but I prefer that to setup_matching. Thoughts?

whatyouhide commented 8 years ago

The idea could work, but I don't like the name setup_if too much; I find it hard to link it to the idea of "matching". Maybe setup_if_matching? It's long, yeah, but I tend not to care too much if variable/function names are long if they convey the meaning in a good way.

josevalim commented 8 years ago

I thought about setup_case and setup_when but the latter on doesn't look good when using guards:

setup_when %{age: age} when age >= 18 do
whatyouhide commented 8 years ago
setup_if_matching %{age: age} when age >= 18 do
  ...
end

looks good to me though. Or maybe setup_if_matches, may work as well.

lexmag commented 8 years ago

What about setup_only? :bowtie:

whatyouhide commented 8 years ago

@lexmag looks a bit alien to me; what would "only" mean?

josevalim commented 8 years ago

What about setup_only? :bowtie:

I think we should go with setup_matching then. :P

lexmag commented 8 years ago

Also I think it would be valuable to come up with a 3-setup use case and see how it will be transformed with new macros.

josevalim commented 8 years ago

We discussed a bit about this during ElixirConf EU. I will post a new report later.

josevalim commented 8 years ago

Here is the new proposal: https://groups.google.com/forum/#!topic/elixir-lang-core/pDI1zvYCj_k

josevalim commented 8 years ago

In master!