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.
Related
I am trying to create a cache of POD6 by precompiling them using the CompUnit set of classes.
I can create, store and retrieve pod as follows:
use v6.c;
use nqp;
my $precomp-store = CompUnit::PrecompilationStore::File.new(prefix=>'cache'.IO);
my $precomp = CompUnit::PrecompilationRepository::Default.new(store=> $precomp-store );
my $key = nqp::sha1('test.pod6');
'test.pod6'.IO.spurt(q:to/CONTENT/);
=begin pod
=TITLE More and more
Some more text
=end pod
CONTENT
$precomp.precompile('test.pod6'.IO, $key, :force);
my $handle = $precomp.load($key, )[0];
my $resurrected = nqp::atkey($handle.unit,'$=pod')[0];
say $resurrected ~~ Pod::Block::Named;
So now I change the POD, how do I use the :since flag? I thought that if :since contains a time after the compilation, then the value of the handle would be Nil. That does not seem to be the case.
my $new-handle = $precomp.load($key, :since('test.pod6'.IO.modified));
say 'I got a new handle' with $new-handle;
Output is 'I got a new handle'.
What I am doing wrong?
Here is a pastebin link with code and output: https://pastebin.com/wtA9a0nP
Module loading code caches look ups and essentially start with:
$lock.protect: {
return %loaded{$id} if %loaded{$id}:exists;
}
So the question becomes "how do I load a module and then unload it (so i can load it again)?" to which the answer is: you cannot unload a module. You can however change the filename, distribution longname ( via changing the name, auth, api, or version ), or precomp id -- whatever a specific CompUnit::Repository uses to uniquely identify modules -- to bypass the cache.
What seems to be overlooked is that $key is intended to represent an immutable name, such that it will always point at the same content. What version of foo.pm should be loaded for Module Used::Inside::A if foo.pm is being loaded by Module A and B at the same time, Module A loads foo first, and then Module B modifies foo? The old version Module A loaded, or the ( possibly conflicting with previous version ) Module B loaded version? And how would this differentiation work for precomp file generation themselves ( which again can happen in parallel )?
Of course if we ignore the above we could add code to make expensive .IO.modified calls for every single module load for all CompUnit::Repository types ( slowing down startup speed ) to say "hey this immutable thing changed". But the granularity of file system modified timestamps on some OS's made the check pretty fragile ( especially for multi-threaded module loading with precomp files being generated ), meaning that even more expensive calls to get a checksum would be necessary every single time.
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'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"
I have an express server which I am testing using vows. I want to run the server from within the vows test suite, so that I dont need to have it running in the background in order for the test suite to work, then I can just create a cake task which runs the server and tests it in isolation.
In server.coffee I have created the (express) server, configured it, set up routes and called app.listen(port) like this:
# Express - setup
express = require 'express'
app = module.exports = express.createServer()
# Express - configure and set up routes
app.configure ->
app.set 'views', etc....
....
# Express - start
app.listen 3030
In my simple routes-test.js I have :
vows = require('vows'),
assert = require('assert'),
server = require('../app/server/server');
// Create a Test Suite
vows.describe('routes').addBatch({
'GET /' : respondsWith(200),
'GET /401' : respondsWith(401),
'GET /403' : respondsWith(403),
'GET /404' : respondsWith(404),
'GET /500' : respondsWith(500),
'GET /501' : respondsWith(501)
}).export(module);
where respondsWith(code) is similar in functionality to the one in the vows doc...
When I require server in the above test, it automatically begins running the server and the tests run and pass, which is great, but I dont feel like I am doing it the 'right' way.
I dont have much control over when the server begins, and what happens if I want to configure the server to point to a 'test' environment rather than the default one, or change the default logging level for when im testing?
PS I am going to convert my vows to Coffeescript but for now its all in js as im in learning mode from the docs!
That is an interesting question because exactly last night I did what you want to do. I have a little CoffeScript Node.js app which happened to be written like the one you showed. Then, I refactored it, creating the following app.coffee:
# ... Imports
app = express.createServer()
# Create a helper function
exports.start = (options={port:3000, logfile:undefined})->
# A function defined in another module which configures the app
conf.configure app, options
app.get '/', index.get
# ... Other routes
console.log 'Starting...'
app.listen options.port
Now I have an index.coffee (equivalent to your server.coffee) as simple as:
require('./app').start port:3000
Then, I wrote some tests using Jasmine-node and Zombie.js. The test framework is different but the principle is the same:
app = require('../../app')
# ...
# To avoid annoying logging during tests
logfile = require('fs').createWriteStream 'extravagant-zombie.log'
# Use the helper function to start the app
app.start port: 3000, logfile: logfile
describe "GET '/'", ->
it "should have no blog if no one was registered", ->
zombie.visit 'http://localhost:3000', (err, browser, status) ->
expect(browser.text 'title').toEqual 'My Title'
asyncSpecDone()
asyncSpecWait()
The point is: what I did and I would suggest is to create a function in a module which starts the server. Then, call this function wherever you want. I do not know if it is "good design", but it works and seems readable and practical to me.
Also, I suspect there is no "good design" in Node.js and CoffeScript yet. Those are brand new, very innovative technologies. Of course, we can "feel something is wrong" - like this situation, where two different people didn't like the design and changed it. We can feel the "wrong way", but it does not mean there is a "right way" already. Summing up, I believe we will have to invent some "right ways" in your development :)
(But it is good to ask about good ways of doing things, too. Maybe someone has a good idea and the public discussion will be helpful for other developers.)
How to change the environment variable of rails in testing
You could do
Rails.stub(env: ActiveSupport::StringInquirer.new("production"))
Then Rails.env, Rails.development? etc will work as expected.
With RSpec 3 or later you may want to use the new "zero monkeypatching" syntax (as mentioned by #AnkitG in another answer) to avoid deprecation warnings:
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("production"))
I usually define a stub_env method in a spec helper so I don't have to put all that stuff inline in my tests.
An option to consider (as suggested in a comment here) is to instead rely on some more targeted configuration that you can set in your environment files and change in tests.
Rspec 3 onwards you can do
it "should do something specific for production" do
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("production"))
#other assertions
end
Sometimes returning a different environment variable can be a headache (required production environment variables, warning messages, etc).
Depending on your case, as an alternate you may be able to simply return the value you need for your test to think it's in another environment. Such as if you wanted Rails to believe it is in production for code that checks Rails.env.production? you could do something like this:
it "does something specific when in production" do
allow(Rails.env).to receive(:production?).and_return(true)
##other assertions
end
You could do the same for other environments, such as :development?, :staging?, etc. If you don't need the full capacity of returning a full environment, this could be another option.
As a simpler variation on several answers above, this is working for me:
allow(Rails).to receive(:env).and_return('production')
Or, for as I'm doing in shared_examples, pass that in a variable:
allow(Rails).to receive(:env).and_return(target_env)
I suspect this falls short of the ...StringInquirer... solution as your app uses additional methods to inspect the environment (e.g. env.production?, but if you code just asks for Rails.env, this is a lot more readable. YMMV.
If you're using something like rspec, you can stub Rails.env to return a different value for the specific test example you're running:
it "should log something in production" do
Rails.stub(:env).and_return('production')
Rails.logger.should_receive(:warning).with("message")
run_your_code
end