Crash with Erlang 18 + Elixir 1.0.5 + meck

1,175 views
Skip to first unread message

Marcin Lewandowski

unread,
Sep 9, 2015, 11:06:28 AM9/9/15
to elixir-lang-talk
Hello,

I've recently encountered an awkward bug that seems for me to be caused by elixir -> erlang compiler. 

I am basically trying to mock something from Supervisor module and it triggers really obscure crash, but only on Erlang 18.
 
Here are some details


I don't think it's an issue in espec, as it's just a syntax sugar over :meck and other stuff, and the error seem to be more low-level.

I apologize if this is not a language issue but just espec problem, but I prefer to announce my doubts.

m.


José Valim

unread,
Sep 9, 2015, 11:17:54 AM9/9/15
to elixir-l...@googlegroups.com
Given I just went on a rant on twitter about mocks, can you please give more context on what are you trying to test and why do you believe you need mocks?

I still haven't needed a single mocking library when writing Elixir code.



José Valim
Skype: jv.ptec
Founder and Director of R&D

--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/cf5cf1f4-2dba-457d-9e98-6fa43329d253%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Alexei Sholik

unread,
Sep 9, 2015, 12:04:50 PM9/9/15
to elixir-l...@googlegroups.com
Looks like allow(Supervisor).to expands to {ESpec.AllowTo, module}.to which no longer works in Erlang 18?

Alexei Sholik

unread,
Sep 9, 2015, 12:05:45 PM9/9/15
to elixir-l...@googlegroups.com
Pardon, the expanded value above should have been this:

{ESpec.AllowTo, Supervisor}.to

Louis Pilfold

unread,
Sep 9, 2015, 12:46:49 PM9/9/15
to elixir-lang-talk
Given mocking is something people feel strongly about, and many of us are
not writing code that lends itself to testing without mocks, perhaps
we could do with some articles or a guide on the subject.

I'm sure there's some good practices to learn for those of us spoilt
by the mocking tools in languages such as Ruby. :)

Cheers,
Louis
> https://groups.google.com/d/msgid/elixir-lang-talk/CAAPY6eO2GT-eHeP%2ByRgjPb06rv8-H8umNoVsZ1616feBO_9ibg%40mail.gmail.com.

Marcin Lewandowski

unread,
Sep 9, 2015, 4:08:59 PM9/9/15
to elixir-lang-talk, jose....@plataformatec.com.br
Hello,

I am willing to test GenServers responsible for handling various types of connections from my app to the external services (with help of Connection library). For instance, my app connects to multiple servers over SSH and routes some information retreived by calling remote commands into websocket channels.

For instance, I want to test if connection params were extracted, transformed and passed correctly to the underlying code responsible for invoking SSH connection without issuing a real connection.

Any suggestions how to do such stuff without mocking?

m. 

Preston Marshall

unread,
Sep 9, 2015, 4:18:58 PM9/9/15
to elixir-l...@googlegroups.com
Use a different module in test env. Make it configurable through OTP Application configuration. I am working on a blog post for this.





Eduardo Gurgel

unread,
Sep 10, 2015, 4:20:33 AM9/10/15
to elixir-l...@googlegroups.com
So the proposed solution is to change production code to be "testable" and making production code to call Application configuration for every function call? This doesn't seem like a good option as it's including a unnecessary overhead to make something "testable".



For more options, visit https://groups.google.com/d/optout.



--
Eduardo

José Valim

unread,
Sep 10, 2015, 5:03:46 AM9/10/15
to elixir-l...@googlegroups.com
So the proposed solution is to change production code to be "testable" and making production code to call Application configuration for every function call? This doesn't seem like a good option as it's including a unnecessary overhead to make something "testable".

It is improving the design of your code.

A test is a user of your API like any other code you write. One of the ideas behind TDD is that tests are code and no different from code. If you are saying "I don't want to make my code testable", you are saying "I don't want to decouple some modules" or "I don't want to think about the contract behind these components".

Just to clarify, there is nothing wrong with "not wanting to decouple some modules". You don't want to decouple every time you call the URI module, for example. But if we are talking about something as complex as an external API, a SSH connection or such, defining a explicit contract and passing the contract as argument is going to make your code wonders and make it easier to manage your app complexity. 

Let's suppose you want to consume the Twitter API in your app. Your integration tests should not mock HTTPoison calls. A very simple way to see this is: if you replace HTTPoison by another HTTP client, should your integration test suite break? Probably not, your app behaviour is still be the same.

Therfore the best course of action is define the Twitter API your application needs in a behaviour:

defmodule MyApp.Twitter do
  @doc "..."
  @callback get_username(username :: String.t) :: %MyApp.Twitter.User{}
  @doc "..."
  @callback followers_for(username :: String.t) :: [%MyApp.Twitter.User{}]
end

Now *you know in a single place all you need from Twitter*. And every time you need to use Twitter,  receive the API as argument or via application configuration:

def twitter_api do
  Application.get_env(:my_app, :twitter_api)
end

Which can be configured per environment as:

# For dev and test
config :my_app, :twitter_api, MyApp.Twitter.Sandbox

Then testing it is a straight-forward consequence of good design.

The best is that you also be able to unit test your Twitter client for production cleanly, let's call it MyApp.Twitter.Prod, because all you need from twitter and what you expect as return values is in a single place and not sprinkled throughout your code. Use the @tag system in ExUnit to not run those tests by default but enable them in your build system:

defmodule MyApp.Twitter.ProdTest do
  use ExUnit.Case, async: true

  # All tests will ping the twitter API
  @moduletag :twitter_api

  ...
end

And in your test helper:

ExUnit.configure exclude: [:twitter_api]

And in your build system:

mix test --include twitter_api

Finally, the rest of your application absolutely does not care what you use to fetch the tweets. This is a good sign you have found the proper boundary between the components.

Note you relying on similar designs *all the time*: when you are using Plug, when you are using Ecto, when testing Phoenix channels, etc.

PS: the overhead is also minimum. Application configuration is stored in ETS tables... which means they are just directly read from memory.

José Valim

unread,
Sep 10, 2015, 5:19:23 AM9/10/15
to elixir-l...@googlegroups.com
One final note: part of testing your system is to find the proper contracts and proper boundaries between the components. If you define the rule that you will only mock if you define a explicit contract, it will:

1. protect you from overmocking because defining contracts for all interactions in your software is verbose and unnecessary (as said, you very likely don't want to hide interactions with the URI module behind a contract)

2. it will make testing your system easier because the contract is explicit (as per the previous post)

3. it will make testing easier because it will push you to move interaction between complex components to a single place.

Take a web application where usually [database] <-> [model] <-> [controller]. This is a pain to test because the complex component, which is the database, is hidden behind two layers, the model and the controller. That's why folks tend to isolate the model and the database. And then the controller and the model. This is such a pain.

Instead, we should make the model layer pure, to work only with data, and move the coordination between complex elements in the controller: [model] <-> [controller] <-> [database]. Then, if you need to isolate from components, it is all in the controller. It already handles the complexity that is the connection, so it is a natural fit for the database, mailer systems... anything that relies on side-effects.

With explicit contracts, you will have the opportunity to ask: maybe I should this contract up in my system instead of burying it inside layers. Your application will always have complexity. So make it explicit and try to manage it a single place instead of everywhere.

4. and it will make it easier to manage the complexity between the components themselves. Because every time you need a new function from twitter, you need to add it to the @callback list. If the list is getting bigger and bigger, it will be obvious and you will be able to act on it




José Valim
Skype: jv.ptec
Founder and Director of R&D

Message has been deleted

Anton Mishchuk

unread,
Sep 10, 2015, 6:07:33 AM9/10/15
to elixir-lang-talk
ESpec creates mocks using Erlang meck library.
One can get the same error in iex console by running:
iex(1)> :meck.new(Supervisor, [:non_strict, :passthrough])
** (EXIT from #PID<0.106.0>) {:compile_forms, {:error, [{'lib/supervisor.ex', [{460, :erl_lint, {:type_syntax, :integer}}, {460, :erl_lint, {:type_syntax, :integer}}, {460, :erl_lint, {:type_syntax, :integer}}, {460, :erl_lint, {:type_syntax, :integer}}]}], []}}

There are caveats in meck's description. One of them is: "Since Erlang have a flat module namespace, replacing a module has to be done globally in the Erlang VM. This means certain modules cannot be mocked."

By the way, the same issue with `Task` module.
iex(1)> :meck.new(Task, [:non_strict, :passthrough])    
** (EXIT from #PID<0.106.0>) {:compile_forms, {:error, [{'lib/task.ex', [{150, :erl_lint, {:type_syntax, :integer}}, {150, :erl_lint, {:type_syntax, :integer}}, {150, :erl_lint, {:type_syntax, :integer}}]}], []}}

Anton Mishchuk

unread,
Sep 10, 2015, 6:08:13 AM9/10/15
to elixir-lang-talk
It does work.

Eduardo Gurgel

unread,
Sep 10, 2015, 6:24:00 AM9/10/15
to elixir-l...@googlegroups.com
Using meck with Task and Supervisor works for me:

defmodule Test do
  use ExUnit.Case

  test "meck!" do
    :meck.expect(Task, :await, 1, :reply)
    :meck.new(Supervisor, [:non_strict, :passthrough])

    assert Task.await(:fake_task) == :reply
  end
end

1 tests, 0 failures


--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Eduardo

Anton Mishchuk

unread,
Sep 10, 2015, 6:30:48 AM9/10/15
to elixir-lang-talk
The issue is when using erlang 18.

Eduardo Gurgel

unread,
Sep 10, 2015, 6:31:26 AM9/10/15
to elixir-l...@googlegroups.com
I'm using Erlang 18 and Elixir 1.0.5


For more options, visit https://groups.google.com/d/optout.



--
Eduardo

Anton Mishchuk

unread,
Sep 10, 2015, 6:33:21 AM9/10/15
to elixir-lang-talk
Ok. I'm using elixir 1.1.0-beta.

Brian Cardarella

unread,
Sep 10, 2015, 7:06:34 AM9/10/15
to elixir-lang-talk, jose....@plataformatec.com.br
José,

Mind if I challenge you with some use cases?

I'm starting to come around to this idea, but I still am not clear how to handle the following:

1. Rate limits - mocking allows us to work around rate limits for external APIs. According to Twitter's doc's it only allows 15 requests per window, each window is 15 minutes (https://dev.twitter.com/rest/public/rate-limiting). Even if we're limiting ourselves to just hitting the external API on the CI server there are still many times when more than 15 PRs have been opened within a 15 minute span for some very large apps we've worked on. Though rare we'd end up with having to wait for the next window, restart the failing tests, and that just pushes out PRs that have been opened in the mean time.

2. Asserting API responses - sticking with Twitter, let's say I release an Elixir Twitter client library as OSS. I will likely not want to share the API key for the account that I am testing against so I make this a Travis environment variable. Others that want to contribute have to use their own Twitter account and set their API key in a similar way in their dev environment. Now if I have the simple test `assert client.username == "bcardarella"` this will work fine for me but fail for others.

I'm sure there are ways to deal with these, I'm curious to know the strategies.

José Valim

unread,
Sep 10, 2015, 7:26:53 AM9/10/15
to elixir-l...@googlegroups.com
Mind if I challenge you with some use cases?

Definitely.  :D
 
I'm starting to come around to this idea, but I still am not clear how to handle the following:

The challenge is to find the proper boundaries. I am assuming that, at this point, we all agree that [MyApp] <-> [Twitter API] is a poor boundary and that [MyApp] <-> [MyApp.Twitter contract] and then [MyApp.Twitter impl for prod] <-> [Twitter API] are better ones.

Therefore, we have effectively removed the dependency of MyApp on the actual Twitter HTTP client which effectively solves both the issues you mentioned below from the perspective of *your application*.

However, we still need to test [My Twitter contract for prod] <-> [Twitter HTTP], right? Ideally, we would like those tests to actually hit twitter whenever we wanted to, because that's the only way to guarantee it works. However, in practice, it may not work. So this may actually be the use case for mocking the HTTP responses as long as we respect the previous properties:

1. if you change your http client, your whole application suite won't break but only the tests for MyApp.Twitter.Prod

2. Don't mock your http client, pass it as a dependency via the app environment, as we have done with the twitter api

3. You could define a contract for this case but it is probably unnecessary as it would be tied to your HTTP client anyway

What do you think?

Josh Adams

unread,
Sep 10, 2015, 7:47:36 AM9/10/15
to elixir-l...@googlegroups.com
> 1. Rate limits - mocking allows us to work around rate limits for external
> APIs. According to Twitter's doc's it only allows 15 requests per window,
> each window is 15 minutes
> (https://dev.twitter.com/rest/public/rate-limiting). Even if we're limiting
> ourselves to just hitting the external API on the CI server there are still
> many times when more than 15 PRs have been opened within a 15 minute span
> for some very large apps we've worked on. Though rare we'd end up with
> having to wait for the next window, restart the failing tests, and that just
> pushes out PRs that have been opened in the mean time.

So the proposed change actually makes this way better. You can have
your typical PR tests just run the contract tests (i.e. with the
exclude in place) and then have an hourly job that runs and tests the
external service. With this separation it's easy to do that sort of
thing - it's just another job in jenkins. Without it, it becomes
exceedingly difficult to do exactly this sort of thing :)

Phani Mahesh

unread,
Sep 10, 2015, 10:21:16 AM9/10/15
to elixir-l...@googlegroups.com
Hello Jose,

You said mocking is bad. If I want to test whether emails are sent on forgot password requests, a simple way is to mock email module and check if the function send is called with the right params. In the absence of mocking, how can I test that the function is being called correctly?


--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.

Paolo D'Incau

unread,
Sep 10, 2015, 10:55:35 AM9/10/15
to elixir-lang-talk, jose....@plataformatec.com.br
Hi José,

if I get it right, in your example you suggest to hide the actual implementation of :twitter_api behind a function that loads, according to the environement a different module. In this way when you are in dev or test you don't use mocks but fakes.

I have got a simple question about this approach:

1) if you follow this strategy for each part of your code you would normally mock, don't you risk to have a very big conf file with a lot of stuff that takes care only of the injection of the correct dependencies?

thanks!

José Valim

unread,
Sep 10, 2015, 10:57:43 AM9/10/15
to elixir-l...@googlegroups.com
Mocking are not inherently bad, they are just a tool, but they are frequently overused. I am writing a post on the topic but generally you could do the following. Imagine this example in Elixir where some function may perform heavy work which you want to isolate in tests:

```elixir
defmodule MyModule do
  def my_function do
    # ...
    SomeDependency.heavy_work(arg1, arg2)
    # ...
  end
end
```

Your test should never do this:

```elixir
test "my function performs heavy work" do
  mock(SomeDependency, :heavy_work, to_return: true)
  MyModule.my_function()
end
```

Because that is changing how `SomeDependency` works *globally*, in your whole codebase. This is particular aggravating on Elixir as changing global values means you can no longer run that part of your suite concurrently.

Instead, pass the dependency around. Passing locally can be done in multiple ways. If your dependency surface is tiny, an anonymous function will suffice:

```elixir
defmodule MyModule do
  def my_function(heavy_work \\ &SomeDependency.heavy_work/2) do
    # ...
    heavy_work.(arg1, arg2)
    # ...
  end
end
```

And in your test:

```elixir
test "my function performs heavy work" do
  heavy_work = fn _, _ ->
    # Simulate heavy work by sending self() a message
    send self(), :heavy_work
  end

  MyModule.my_function(heavy_work)

  assert_received :heavy_work
end
```

Or define the contract, as I explained in the earlier e-mails, and pass a module in:

```elixir
defmodule MyModule do
  def my_function(dependency \\ SomeDependency) do
    # ...
    dependency.heavy_work(arg1, arg2)
    # ...
  end
end
```

Now in your test:

```elixir
test "my function performs heavy work" do
  # Simulate heavy work by sending self() a message
  defmodule TestDependency do
    def heavy_work(_arg1, _arg2) do
      send self(), :heavy_work
    end
  end

  MyModule.my_function(TestDependency)

  assert_received :heavy_work
end
```

TestDependency in this example is a mock. Except you define it manually instead of using fancy libraries. If you want to check for arguments, you can hardcode them in the TestDependency:

    def heavy_work(:shuold_be_1, :should_be_2) do
      send self(), :heavy_work
    end

Or even send them as a message so you check later:

    def heavy_work(arg1, arg2) do
      send self(), {:heavy_work, arg1, arg2}
    end

If you can't pass the argument, use the Applicaiton.get_env approach discussed earlier.

Keep in mind, as mentioned before, the issue is in finding the boundaries you are comfortable with. For example, instead of replacing the module that sends the e-mail, you could still do the whole job of rendering the e-mail but, instead of calling something like mailgun to send it, you call another backend that would deliver the message to the test process as shown above. So you'd just replace the backend you use for delivery between dev/test/prod. And one of those backends just send self() a message.


José Valim
Skype: jv.ptec
Founder and Director of R&D

José Valim

unread,
Sep 10, 2015, 11:04:31 AM9/10/15
to elixir-l...@googlegroups.com
Excellent question Paolo!

I agree with you. That's why I said part of the trouble is finding the boundaries. If you break in too many boundaries, then not only your conf file will be huge, but your test suite may become more brittle, as you are increasingly testing mocked or faked components. If you don't break enough, you can suffer from a lot of coupling and/or slow tests.

That's also why I like to move the interaction between those components to a single place and avoid the dependency between multiple complex components which would grow in a combinatory fashion (I think? someone needs to do the math). 

Finally, the config file is not the only approach. In the previous approach I showed a local one where you pass it as argument. :)

Paolo D'Incau

unread,
Sep 10, 2015, 11:14:23 AM9/10/15
to elixir-lang-talk, jose....@plataformatec.com.br
Thanks for your answer,


I agree with you. That's why I said part of the trouble is finding the boundaries. If you break in too many boundaries, then not only your conf file will be huge, but your test suite may become more brittle, as you are increasingly testing mocked or faked components. If you don't break enough, you can suffer from a lot of coupling and/or slow tests.

agreed, finding the correct boundaries is the key part. I would add that different developers will probably find different boundaries according to taste and experience.

Finally, the config file is not the only approach. In the previous approach I showed a local one where you pass it as argument. :)

Yes, but I have to say that I don't like very much to use default values because in this way you are modifying your application code only for testing purposes.

BTW my guess is that people coming from other languages (like me) usually use mocks extensively and try to apply the same approach in Elixir, where probably this is not the idiomatic way of doing things...that's why I am looking forward to read your blog post.

José Valim

unread,
Sep 10, 2015, 11:20:37 AM9/10/15
to Paolo D'Incau, elixir-lang-talk
 
I agree with you. That's why I said part of the trouble is finding the boundaries. If you break in too many boundaries, then not only your conf file will be huge, but your test suite may become more brittle, as you are increasingly testing mocked or faked components. If you don't break enough, you can suffer from a lot of coupling and/or slow tests.

agreed, finding the correct boundaries is the key part. I would add that different developers will probably find different boundaries according to taste and experience.

Yes, exactly!

Yes, but I have to say that I don't like very much to use default values because in this way you are modifying your application code only for testing purposes.

It was a quote like this that started my replies on this thread. :) I will copy it again:

> A test is a user of your API like any other code you write. One of the ideas behind TDD is that tests are code and no different from code. If you are saying "I don't want to make my code testable", you are saying "I don't want to decouple some modules" or "I don't want to think about the contract behind these components".

In other words, if you would add a default argument because some part of your code would need it, adding the default argument should then also be valid when that part of the code is the test.
  
BTW my guess is that people coming from other languages (like me) usually use mocks extensively and try to apply the same approach in Elixir, where probably this is not the idiomatic way of doing things...that's why I am looking forward to read your blog post.

Hopefully next week it is out. I had no idea this was such a hot topic! :D 

Saša Jurić

unread,
Sep 10, 2015, 12:05:47 PM9/10/15
to elixir-lang-talk, paolo....@gmail.com, jose....@plataformatec.com.br

It was a quote like this that started my replies on this thread. :) I will copy it again:

> A test is a user of your API like any other code you write. One of the ideas behind TDD is that tests are code and no different from code. If you are saying "I don't want to make my code testable", you are saying "I don't want to decouple some modules" or "I don't want to think about the contract behind these components".

In other words, if you would add a default argument because some part of your code would need it, adding the default argument should then also be valid when that part of the code is the test.


I'm going to go all heretic and challenge this assumption, or at least say I don't think it applies in all cases. The twitter example is an excellent case where loose coupling and abstract interface-based approach works better. However, on a lower level scale, dependency injection may in fact obfuscate things.

Let's take a look at this code:

def my_function(heavy_work \\ &SomeDependency.heavy_work/2) do
  # ...
  heavy_work.(arg1, arg2)
  # ...
end

How can I know what is in fact being invoked here? I can't without knowing the call stack (or going to the configuration file). This decreases my reading experience, and as we all know, we read the code way more often than we write it.

In contrast, if I see that the function Bar.baz is called, the relationship is pretty clear. Such code is straightforward, not burdened with artifical indirections and easier to follow. Hence, I like to keep the lower level code as direct and straightforward as possible. Loose coupling seems more suitable as I move up the hierachy, and make different parts of the system (or different systems) communicate.

Test is indeed a user of the API. However, since unit tests tend to focus on low-level isolated tests, it is usually the "deliberately introduced second user of the API". I'm not saying this is bad or anything, just that without tests lot of the code has exactly one user. Adding dependency injections all over the code for the purposes of a single user (which btw. doesn't run in production), sounds a bit dubious to me.

I agree that mocking has it's issues and is probably overused. That being said, I use it to isolate lower level tight dependencies, such as one GenServer calling another. I understand the global issues, but usually don't mind them. Most often, when I develop, I run isolated suits anyway, and these tend to run pretty quickly without needing concurrency. Once I'm happy with individual changes, I don't mind if a whole run takes longer, since I don't run it all that often. 

Greg Vaughn

unread,
Sep 10, 2015, 12:45:41 PM9/10/15
to elixir-l...@googlegroups.com
I'd like to add another general point that goes along with this component boundary manual mocking approach: the granularity of the tests themselves. I'm assuming we're all comfortable with tests of pure functions. It's the impure parts of the system that have side effects where things get tricky.

Overuse of mocking (which I've seen in Ruby, Javascript, C#, Java, etc.) leads to very brittle tests. I've wanted to do a true refactoring (not changing external behavior of the app) that broke a whole lot of tests -- because they were testing implementation details instead of observable external behavior. We, as an industry, don't do a good enough job deleting tests that are no longer useful. Those fine-grained tests were really helpful for the initial build out of the feature, but then new tests that test observable behavior at a coarser grain should replace them.

Some of this is tied to an OO vs. FP approach. In an OO world, you have little pieces of state in every object. Those are technically "observable behavior" that can be asserted against. However, in Elixir, what are the "observable behaviors"? 1) I/O, 2) messages sent/received, 3) process state (am I missing any?). Those are the pieces to focus test assertions on.

1) I/O is a perfect match for the manual mocking approach like the TwitterAPI example in this thread.

2) Message assertions require you to figure out some way to make the test process itself stand-in for some actor in your system, such as explicitly passing a pid of a collaborator into a start_link function, or looking up collaborators by registered names, or use of default parameters that are explicitly passed in tests.

3) Asserting on process state would not be my first preference, but it can be done with :erlang.process_info (which should ONLY be used in test code).

The point of good design in the erlang ecosystem is to have most of your system pure and isolate the impure parts to a few areas. Those few areas are probably going to be natural component boundaries.

-Greg Vaughn
> --
> You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/CAGnRm4JN%2BiQWodpNkYLd60_M6M0fQNgEgrU%3DzXg7%2BLSWSAmn%2Bw%40mail.gmail.com.

ritchie turner

unread,
Sep 10, 2015, 2:26:03 PM9/10/15
to elixir-lang-talk

Talking of message assertions,  I reccently watched a Joe Armstrong talk, where he mentioned his UBF format which has a built in contract checker

https://www.sics.se/~joe/ubf/site/home.html

The point being, and tieing in with Greg's post on granularity, it's the messages, and their state machines that are more important than the black box processes.

Worth reading (of course) :).

R

Peter Saxton

unread,
Oct 13, 2015, 9:06:15 AM10/13/15
to elixir-lang-talk
This is a very interesting read and I agree with most of the points raised.

I think global config is a good solution for large external services. The twitter api client for example seams to make perfect sense.
However the problem I have is with integration/acceptance tests. I would like to run 90% of my test suite with the MyApp.Twitter.Sandbox but then run the remaining 10% with MyApp.Twitter.Prod to ensure all the peices are working together. I really don't want to be passing around this as a dependency.

Would the suggestion be.

a) use behaviours to define my contract and assume that this is good enough that I don't need integration tests (bit uncomfortable with this, its not impossible that I forgot to even set the behaviour in one of my implementations)

b) can't really think of an alternative

Second. I find myself passing round both funcationality and data if I use the dependency injection option.

defmodule MyServer do
  def start_link(url, connection_module \\ Connection) do
   connection = connection_module.open
   Genserver.start_link(__MODULE__, {connection_,module, connection, 0})
  end

  def send(server, message) do
    GenServer.call(server, message)
  end

  def handle_call({:send, message}, _from, {connection_module, connection, count}) do
    message = set_counter(message, count)
    connection_module.send(connection, message)
    {:ok, {;ok, message}, {connection_module, connection, count + 1}}
  end
end

This seams messy and overkill.

Peter Saxton

unread,
Oct 13, 2015, 9:11:50 AM10/13/15
to elixir-lang-talk
To prevent passing functonality and half the arguments maybe I should pass part applied functions.



  def start_link(url, connection_module \\ Connection) do
   connection = connection_module.open
    send = &connection_module(connection, &1)
   Genserver.start_link(__MODULE__, {send, 0})
  end

Then in the handle sending a message body I can just use the 'message |> set_count(count) |> send'

Louis Pilfold

unread,
Oct 13, 2015, 9:21:25 AM10/13/15
to elixir-lang-talk
I've been running certain tests with async: false, having them change
global state at the start of the test case, and reset it to the
original state afterwards.

Cheers,
Louis
> --
> You received this message because you are subscribed to the Google Groups
> "elixir-lang-talk" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to elixir-lang-ta...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/elixir-lang-talk/8a81ae8b-6c0d-4463-936b-d57b72c4a941%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages