I'm using ExUnit for testing my Elixir app, which is a card game.
I find that with every test I write, I start by creating a fresh deck of cards.
test "Do This Crazy Thing do
deck = Deck.create()
[...]
end
test "Do This Other Crazy Unrelated Thing" do
deck = Deck.create()
[...]
end
Is there a way to factor this out so that a new deck can just be created before every test case? I know there's something close to this with setup do [...] end, but I don't think that's the solution for me.
Do I need a different test framework? Do I need to use setup in some way I haven't thought of yet?
-Augie
You can use def setup with the meta has just for this.
Example:
defmodule DeckTest do
use ExUnit.Case
setup do
{:ok, cards: [:ace, :king, :queen] }
end
test "the truth", meta do
assert meta[:cards] == [:ace, :king, :queen]
end
end
Here's some more info
Another option that could work depending on your needs:
defmodule DeckTest do
use ExUnit.Case
defp cards, do: [:ace, :king, :queen]
test "the truth" do
assert cards == [:ace, :king, :queen]
end
end
Related
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.
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.
Is it possible to rename an existing Erlang module? I have some code in several Erlang modules that I want to use in an Elixir project (assertion library).
I don't want to convert all the Erlang modules to Elixir as they are complete, but I want to rename them, and possible add additional functions to them. Both modules are simply collections of functions, they don't implement behaviors or do anything unusual.
I want to be able to take an existing Erlang module:
-module(foo_erl).
-export([some_fun/1]).
some_fun(Arg) ->
ok.
And write an Elixir module to extend the Erlang one:
defmodule ExFoo do
somehow_extends_erlang_module :foo_erl
another_fun(arg) do
:ok
end
end
And then be able to use the functions defined in the Erlang module:
iex(1)> ExFoo.some_fun(:arg) #=> :ok
Is this possible in Elixir? If so, how would I go about doing this?
Here's the first thing that I can suggest. I'm 100% sure that it could be solved in a more elegant way. I'll back to this later since I'm not an Elixir guru yet.
defmodule Wrapper do
defmacro __using__(module) do
exports = module.module_info[:exports] -- [module_info: 0, module_info: 1]
for {func_name, arity} <- exports do
args = make_args(arity)
quote do
def unquote(func_name)(unquote_splicing(args)) do
unquote(module).unquote(func_name)(unquote_splicing(args))
end
end
end
end
defp make_args(0), do: []
defp make_args(arity) do
Enum.map 1..arity, &(Macro.var :"arg#{&1}", __MODULE__)
end
end
defmodule WrapperTest do
use ExUnit.Case, async: true
use Wrapper, :lists
test "max function works properly" do
assert (max [1, 2]) == 2
end
end
In general, we suggest to avoid wrapping Erlang modules. In case you want to wrap many modules in an automatic fashion, velimir's post is right on the spot.
If you have one-off case though, where you definitely want to wrap an Erlang module, I would suggest to simply use defdelegate:
defmodule MyLists do
defdelegate [flatten(list), map(fun, list)], to: :lists
end
I'm just starting to learn the practice of BDD / TDD (world rejoices, I know). One of the things that I struggle with at this point is which tests are actually worth writing. Let's take these set of tests which I started for a model called Sport:
Factory.define :sport do |f|
f.name 'baseball'
end
require 'spec_helper'
describe Sport do
before(:each) do
#sport_unsaved = Factory.build(:sport) # returns an unsaved object
#sport_saved = Factory.create(:sport) # returns a saved object
end
# Schema testing.
it { should have_db_column(:name).of_type(:string) }
it { should have_db_column(:created_at).of_type(:datetime) }
it { should have_db_column(:updated_at).of_type(:datetime) }
# Index testing.
# Associations testing.
it { should have_many(:leagues) }
# Validations testing.
it 'should only accept unique names' do
#sport_unsaved.should validate_uniqueness_of(:name)
end
it { should validate_presence_of(:name) }
it 'should allow valid values for name' do
Sport::NAMES.each do |v|
should allow_value(v).for(:name)
end
end
it 'should not allow invalid values for name' do
%w(swimming Hockey).each do |v|
should_not allow_value(v).for(:name)
end
end
# Methods testing.
end
Few specific questions that I have:
Is it worth testing that the association sport.leagues returns a non-blank value?
How about a test that ensure the model is invalid if a name is not specified?
How about a test to make sure that a valid record is created and doesn't have any validation errors?
I could go on. Ideally, there would be some hard and fast rules I could follow to guide my testing effort. But I am guessing this comes with experience and good ole' pragmatism. I've thought about reading through the source code of several gems such as the rails core to gain a better understanding of what's worth testing and what isn't.
Any advice you experienced testers out there could offer?
Not if you're only re-testing Rails behavior.
Yes--it's part of model validation and a requirement, why not make sure the requirement is met?
Testing your assumptions regarding the save process is a good idea, and if there are any lifecycle listeners/observers they may not be fired until the save.
The Rails core tests won't help you decide what's a good idea to test in an application.
What should you test ? Anything you would not want to be broken
When to stop writing tests ? When fear transforms into boredom
So if 1,2,3 are defects if the specified behavior is not exhibited, then you should have tests on all 3.
From the code snippets, personally I'd refrain from checking DB implementation (which columns exist and their details). Reason: I'd want to be able to change that over time without having to break a bunch of tests and fix all of them. Tests should only break if the behavior is broken. If the way (implementation) in which you satisfy them changes, the tests should not break/need modifications.
Focus on the What over the How.
HTH
I've got the following model
class User < ActiveRecord::Base
before_create :set_some_values
private
def set_some_values
#do something
end
end
In specs I'm using Fabrication gem to create objects but I can't find a way to stub the set_some_values method. I tried
User.any_instance.stub!(:set_some_values).and_return(nil)
but Fabrication seems to ignore this. Is it possible to do?
This is why I don't like ActiveRecord callbacks -- because if you want to have nothing to do with a callback (because, say, you're making a call to an external service inside the callback) you still have to be concerned about stubbing it out. Yes you could stub out methods inside the callback, but it's the same problem, and actually it's a bit worse because now you are concerned about something inside a method you want nothing to do with.
As usual there are multiple options here.
One option which I've used a lot in the past is, add a condition to your callback that turns it off by default. So your Post class could look like:
class Post
before_save :sync_with_store, :if => :syncing_with_store?
def syncing_with_store?; #syncing_with_store; end
attr_writer :syncing_with_store
def sync_with_store
# make an HTTP request or something
end
end
Now wherever you really want to call the callback (perhaps in your controller or wherever), you can set post.syncing_with_store = true before you call post.save.
The downside to this approach is, it's something that you (and other devs working with you) have to keep in mind, and it's not really obvious that you have to do this. On the other hand, if you forget to do this, nothing bad happens.
Another option is to use a fake class. Say you have a Post that pushes its data to an external data store on save. You could extract the code that does the pushing to a separate class (e.g. Pusher) which would be accessible at Post.pusher_service. By default, though, this would be set to a fake Pusher class that responds to the same interface but does nothing. So like:
class Post
class << self
attr_accessor :pusher_service
end
self.pusher_service = FakePostPusher
before_save :sync_with_store
def sync_with_store
self.class.pusher_service.run(self)
end
end
class FakePostPusher
def self.run(post)
new(post).run
end
def initialize(post)
#post = post
end
def run
# do nothing
end
end
class PostPusher < FakePostPusher
def run
# actually make the HTTP request or whatever
end
end
In your production environment file, you'd set Post.pusher_service = Pusher. In individual tests or test cases, you'd make a subclass of Post -- let(:klass) { Class.new(Post) } -- and set klass.pusher_service = Pusher (that way you don't permanently set it and affect future tests).
The third approach, which I have been experimenting with, is this: simply don't use ActiveRecord callbacks. This is something I picked up from Gary Bernhardt's screencasts (which, by the way, are pretty amazing). Instead, define a service class that wraps the act of creating a post. Something like:
class PostCreator
def self.run(attrs={})
new(attrs).run
end
def initialize(attrs={})
#post = Post.new(attrs)
end
def run
if #post.save
make_http_request
return true
else
return false
end
end
def make_http_request
# ...
end
end
This way PostCreator.run(attrs) is the de facto way of creating a post instead of going through Post. Now to test saves within Post, there's no need to stub out callbacks. If you want to test the PostCreator process, there's no magic going on, you can easily stub out whichever methods you want or test them independently. (You could argue that stubbing out methods here is the same as stubbing out AR callbacks, but I think it's more explicit what's going on.) Obviously this only handles post creation, but you could do the same for post updating too.
Anyway, different ideas, pick your poison.
The #set_some_values method here is called when you call #save on the record. So it has nothing to do with the constructor and therefore you don't need to stub User.any_instance -- just make your record and then do a partial stub, as in:
record.stub(:set_some_values)
record.save