How do I test a module when the behaviour depends on Application.get_env/3? - testing

I'm making a library with a module that when use'd injects some functions dependant on the contents of a directory, and I want to test the behaviour with different directories. Currently I get the path to the directory through application config with Application.get_env/3.
If I'm changing the directory Application.put_env/4 it means my tests have to run sequentially as this is effective a global value, correct?
Can I stub out the call to Application.get_env/3? Or should I be passing in the value in another way? (such as via the use macro)

The simplest way is to pass in the value as an argument. Your module could depend on Application.get_env only absent a passed in value. Something like:
Application.put_env(MyApplication, :some_key, "hello")
defmodule Test do
def test(string \\ Application.get_env(MyApplication, :some_key)) do
IO.inspect(string)
end
end
# Default behaviour
Test.test # => "hello"
# In your tests
Test.test("world") # => "world"

Related

How can I dynamically generate test cases with common test?

With Common Test test suites, it looks like test cases must be 1:1 with atoms that correspond to top-level functions in the suite. Is this true?
In that case, how can I dynamically generate test cases?
In particular, I want to read a directory, and then, (in parallel) for each file in the directory, do stuff with the file and then compare against a snapshot.
I got the parallelization I wanted with rpc:pmap, but what I don't like is that the entire test case fails on the first bad assert. I want to see what happens with all the files, every time. Is there a way to do this?
Short answer: No.
Long answer: No. I even tried using Ghost Functions
-module(my_test_SUITE).
-export [all/0].
-export [has_files/1].
-export ['$handle_undefined_function'/2].
all() -> [has_files | files() ].
has_files(_) ->
case files() of
[] -> ct:fail("No files in ~s", [element(2, file:get_cwd())]);
_ -> ok
end.
files() ->
[to_atom(AsString) || AsString <- filelib:wildcard("../../lib/exercism/test/*.test")].
to_atom(AsString) ->
list_to_atom(filename:basename(filename:rootname(AsString))).
'$handle_undefined_function'(Func, [_]) ->
Func = file:consult(Func).
And… as soon as I add the undefined function handler, rebar3 ct start reporting…
All 0 tests passed.
Clearly common test is also using the fact that some functions are undefined to work. 🤷‍♂️
Data Directory
Each common test suite can have a "data" directory. This directory can contain anything you want. For example, a test suite mytest_SUITE, can have mytest_SUITE_data/ "data" directory. The path to data directory can be obtained from the Config parameter in test cases.
someTest(Config) ->
DataDir = ?config(data_dir, Config),
%% TODO: do something with DataDir
?assert(false). %% include eunit header file for this to work
Running tests in parallel
To run tests in parallel you need to use groups. Add a group/0 function to the test suite
groups() -> ListOfGroups.
Each member in ListOfGroups is a tuple, {Name, Props, Members}. Name is an atom, Props is list of properties for the groups, and Members is a list of test cases in the group. Setting Props to [parallel|OtherProps] will enable the test cases in the group to be executed in parallel.
Dynamic Test Cases
Checkout cucumberl project.

Testing Elixir/Phoenix Service modules

I've been playing around with Elixir/Phoenix third-party modules. ( Modules that are used to fetch some data from a 3rd party service ) One of those module looking like so:
module TwitterService do
#twitter_url "https://api.twitter.com/1.1"
def fetch_tweets(user) do
# The actual code to fetch tweets
HTTPoison.get(#twitter_url)
|> process_response
end
def process_response({:ok, resp}) do
{:ok, Poison.decode! resp}
end
def process_response(_fail), do: {:ok, []}
end
The actual data doesn't matter in my question. So now, I'm interested in how can I dynamically configure the #twitter_url module variable in tests to make some of the tests fail on purpose. For example:
module TwitterServiceTest
test "Module returns {:ok, []} when Twitter API isn't available"
# I'd like this to be possible ( coming from the world of Rails )
TwitterService.configure(:twitter_url, "new_value") # This line isn't possible
# Now the TwiterService shouldn't get anything from the url
tweets = TwitterService.fetch_tweets("test")
assert {:ok, []} = tweets
end
end
How can I achieve this?
Note: I know I can use :configs to configure #twiter_url separately in dev and test environments, but I'd like to be able to test on a real response from the Twitter API too, and that would change the URL on the entire Test environment.
One of the solutions that I came up with was
def fetch_tweets(user, opts \\ []) do
_fetch_tweets(user, opts[:fail_on_test] || false)
end
defp _fetch_tweets(user, [fail_on_test: true]) do
# Fails
end
defp _fetch_tweets(user, [fail_on_test: false]) do
# Normal fetching
end
But that just seems hackish and silly, there must be a better solution to this.
As it was suggested by José in Mocks And Explicit Contracts, the best way would be probably to use a dependency injection:
module TwitterService do
#twitter_url "https://api.twitter.com/1.1"
def fetch_tweets(user, service_url \\ #twitter_url) do
# The actual code to fetch tweets
service_url
|> HTTPoison.get()
|> process_response
end
...
end
Now in tests you just inject another dependency when necessary:
# to test against real service
fetch_tweets(user)
# to test against mocked service
fetch_tweets(user, SOME_MOCK_URL)
This approach will also make it easier to plug in different service in the future. The processor implementation should not depend on it’s underlying service, assuming the service follows some contract (responds with json given a url in such a particular case.)
config sounds like a good way here. You can modify the value in the config at runtime in your test and then restore it after the test.
First, in your actual code, instead of #twitter_url, use Application.get_env(:my_app, :twitter_url).
Then, in your tests, you can use a wrapper function like this:
def with_twitter_url(new_twitter_url, func) do
old_twitter_url = Application.get_env(:my_app, :twitter_url)
Application.set_env(:my_app, :twitter_url, new_twitter_url)
func.()
Application.set_env(:my_app, :twitter_url, old_twitter_url)
end
Now in your tests, do:
with_twitter_url "<new url>", fn ->
# All calls to your module here will use the new url.
end
Make sure you're not using async tests for this as this technique modifies global environment.

Rails 3.2.5 in `eval': wrong number of arguments (0 for 2..3) (ArgumentError)

I wrote a runner (saved in lib folder). When starting the runner with: rails runner lib/test.rb
def aaa
puts "aaa"
end
aaa
it dumps:
in `eval': wrong number of arguments (0 for 2..3) (ArgumentError)
why?
rails runner is intended to run code from your app codebase as in
(from the guide)
rails runner "Model.long_running_method" # parses the string and executes
# the (hypothetical) method [long_running_method]
# from (hypothetical) model [app/models/model.rb]
the error raises from the fact that in your call you don't provide a string to evaluate
anyway to make it work this way (with a function from lib) you should
enclose your method in some class and
make the class available requiring it someway during application boot
! pay attention: if you call rails runner 'MyClass.my_method' you're calling a class method which has to be defined properly
def self.my_method
# your code
end
if you want to call an instance method you need to do rails runner 'MyClass.new.my_method'
All that said, rails runner boots all the rails app.
If that is not required, may I suggest to investigate whether a rake task could be suited for your needs ?

Define a constant module in elixir

I would like to create some changeable boundary module. Ideally the result would look like any other module but the behaviour could be set at compile time or in the configuration files. I think I am looking for something like define in erlang
Say I have a SystemClock module and a DummyClock tuple module. Ideally the Clock module would be one or other of the two modules above chosen in the config file.
In config/test.ex
define(Clock, {DummyClock, 12345678})
Later
Clock.now
# => 12345678
In config/prod.ex
define(Clock, SystemClock)
Later
Clock.now
# => 32145687
I think the easiest way to do this is with configs and Application.get_env/2.
in config/test.exs
config :my_application, clock: DummyClock
in config/dev.exs and config/prod.exs
config :my_application, clock: RealClock
in the code that uses the clock
defp clock, do: Application.get_env(:my_application, :clock)
def my_function(arg1, arg2) do
now = clock.now
# ...
end
An option that I've used many times is Meck, which you can get/read about here. For your example, we might write something like:
test "something that depends on Clock calls Clock.now" do
:meck.new(Clock, [])
:meck.expect(Clock, :now, fn -> 12345678 end)
# we expect that the module under test is getting it's result from Clock
assert ModuleUnderTest.execute == 12345678
end
Instead of re-compiling your code to get :test behavior, Meck replaces the functionality of your modules on the fly, during your tests.
The discussion referenced in other comments here points out some reasons not to use mocking; Yes, you have to sacrifice certain things (sequential testing, and José's opinion of your testing code), but in exchange, you can dramatically simplify testing code in tricky situations.

How to set a condition with #auth.requires_login in Web2py

In my web2py web app, the controller function def index(): has the decorator #auth.requires_login().
For development and testing purpose I comment out this decorator. But I often forget to uncomment it before committing the code.
I would like to change this decorator into something that would test if a file is present (i.e. skipLogin) and if not to require login. I can then add the skipLogin file in my .gitignore file and don't need to worry about commenting and uncommenting the decorator line.
I suppose it should be something like #auth.requires(lambda: xxx) but I have no idea what xxx should be.
auth.requires takes a requires_login argument, so you can just set the condition to True, and then conditionally set the value of requires_login. For example, you can set it to require login for non-local requests:
#auth.requires(True, requires_login=not request.is_local)