- Notifications
You must be signed in to change notification settings - Fork1
Mocking library for Elixir language
License
techgaun/mock
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
A mocking library for the Elixir language.
We use the Erlangmeck library to providemodule mocking functionality for Elixir. It uses macros in Elixir to expose thefunctionality in a convenient manner for integrating in Elixir tests.
See the fullreference documentation.
First, add mock to yourmix.exs
dependencies:
defdepsdo[{:mock,"~> 0.3.0",only::test}]end
and run$ mix deps.get
.
The Mock library provides thewith_mock
macro for running tests withmocks.
For a simple example, if you wanted to test some code which callsHTTPotion.get
to get a webpage but without actually fetching thewebpage you could do something like this.
defmoduleMyTestdouseExUnit.Case,async:falseimportMocktest"test_name"dowith_mockHTTPotion,[get:fn(_url)->"<html></html>"end]doHTTPotion.get("http://example.com")# Tests that make the expected callassert_calledHTTPotion.get("http://example.com")endendend
You can also assert for number of counts for a call to happen
assertcalled1,HTTPotion.get("http://example.com")assert_called1,HTTPotion.get("http://example.com")
And you can mock up multiple modules withwith_mocks
.
opts
List of optional arguments passed to meck.:passthrough
willpassthrough arguments to the original module. Pass[]
asopts
if you don'tneed this.
defmoduleMyTestdouseExUnit.Case,async:falseimportMocktest"multiple mocks"dowith_mocks([{Map,[],[get:fn(%{},"http://example.com")->"<html></html>"end]},{String,[],[reverse:fn(x)->2*xend,length:fn(_x)->:okend]}])doassertMap.get(%{},"http://example.com")=="<html></html>"assertString.reverse(3)==6assertString.length(3)==:okendendend
You can mock functions that return different values depending on the input:
defmoduleMyTestdouseExUnit.Case,async:falseimportMocktest"mock functions with multiple returns"dowith_mocks(HTTPotion,[get:fn"http://example.com"->"<html>Hello from example.com</html>""http://example.org"->"<html>example.org says hi</html>"end])doassertHTTPotion.get("http://example.com")=="<html>Hello from example.com</html>"assertHTTPotion.get("http://example.org")=="<html>example.org says hi</html>"endendend
You can mock functions in the same module with different arity.The same way you could mock function with optional args.
defmoduleMyTestdouseExUnit.Case,async:falseimportMocktest"mock functions with different arity"dowith_mockString,[slice:fn(string,range)->stringend,slice:fn(string,range,len)->stringend]doassertString.slice("test",1..3)=="test"assertString.slice("test",1,3)=="test"endendend
An additional convenience macrotest_with_mock
is supplied whichinternally delegates towith_mock
. Allowing the above test to bewritten as follows:
defmoduleMyTestdouseExUnit.Case,async:falseimportMocktest_with_mock"test_name",HTTPotion,[get:fn(_url)->"<html></html>"end]doHTTPotion.get("http://example.com")assert_calledHTTPotion.get("http://example.com")endend
Thetest_with_mock
macro can also be passed a context argumentallowing the sharing of information between callbacks and the test
defmoduleMyTestdouseExUnit.Case,async:falseimportMocksetupdodoc="<html></html>"{:ok,doc:doc}endtest_with_mock"test_with_mock with context",%{doc:doc},HTTPotion,[],[get:fn(_url,_headers)->docend]doHTTPotion.get("http://example.com",[foo::bar])assert_calledHTTPotion.get("http://example.com",:_)endend
Thewith_mock
creates a mock module. The keyword list provides a setof mock implementation for functions we want to provide in the mock (inthis case justget
). Insidewith_mock
we exercise the test codeand we can check that the call was made as we expected usingcalled
andproviding the example of the call we expected (the second argument:_
has aspecial meaning of matching anything).
You can also pass the option:passthrough
to retain the original modulefunctionality. For example
defmoduleMyTestdouseExUnit.Case,async:falseimportMocktest_with_mock"test_name",IO,[:passthrough],[]doIO.puts"hello"assert_calledIO.puts"hello"endend
Thesetup_with_mocks
mocks up multiple modules prior to every single testalong with calling the provided setup block. It is simply an integration of thewith_mocks
macro available in this module along with thesetup
macro defined in elixir'sExUnit
.
defmoduleMyTestdouseExUnit.Case,async:falseimportMocksetup_with_mocks([{Map,[],[get:fn(%{},"http://example.com")->"<html></html>"end]}])dofoo="bar"{:ok,foo:foo}endtest"setup_with_mocks"doassertMap.get(%{},"http://example.com")=="<html></html>"endend
The behaviour of a mocked module within the setup call can be overridden using anyof the methods above in the scope of a specific test. Providing this functionalitybysetup_all
is more difficult, and as such,setup_all_with_mocks
is not currentlysupported.
Currently, mocking modules cannot be done asynchronously, so make sure that youare not usingasync: true
in any module where you are testing.
Also, because of the way mock overrides the module, it must be defined in aseparate file from the test file.
The use of mocking can be somewhat controversial. I personally think that itworks well for certain types of tests. Certainly, you should not overuse it. Itis best to write as much as possible of your code as pure functions which don'trequire mocking to test. However, when interacting with the real world (or webservices, users etc.) sometimes side-effects are necessary. In these cases,mocking is one useful approach for testing this functionality.
Also, note that Mock has a global effect so if you are using Mocks in multipletests setasync: false
so that only one test runs at a time.
Open an issue.
I'd welcome suggestions for improvements or bugfixes. Just open an issue.
About
Mocking library for Elixir language
Topics
Resources
License
Stars
Watchers
Forks
Packages0
Languages
- Elixir100.0%