Hi Anselm,<br>
<br>
Thanks - that's really helpful - I'll take a look :-)<br>
<br>
James.<br><br><div><span class="gmail_quote">On 08/09/06, <b class="gmail_sendername">Anselm Heaton</b> <<a href="mailto:anselm@netuxo.co.uk">anselm@netuxo.co.uk</a>> wrote:</span><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<div style="direction: ltr;">Hello :)<br><br>Having just gone through the (admittedly simple) API of Mocha to learn<br>how it works, I wrote a quick guide (190 lines) to help others get up to<br>speed. I've attached it as RDOC and as HTML.
<br><br>It is a first draft - please help me improve by giving feedback !<br><br>Contents :<br>Overview - Quick overview of Mocha<br>Unit testing, mock object and stubs - Very quick introduction to mock<br>objects and stubs
<br>Example with Mocha - The core of the guide ; shows how to use Mocha with<br>a continuing example<br>The Mocha library - How the library is structured<br>Mocha tips - Some tips I would've found useful at first :)<br><br>
Best,<br>Anselm<br><br>--<br>------------------------------<br>Netuxo Ltd<br>a workers' co-operative<br>providing low-cost IT solutions<br>for peace, environmental and social justice groups<br>and the radical NGO sector<br>
<br>Registered as a company in England and Wales. No 4798478<br>Registered office: 5 Caledonian Road, London N1 9DY, Britain<br>------------------------------<br>email <a onclick="return top.js.OpenExtLink(window,event,this)" href="mailto:office@netuxo.co.uk">
office@netuxo.co.uk</a><br><a onclick="return top.js.OpenExtLink(window,event,this)" href="http://www.netuxo.co.uk" target="_blank">http://www.netuxo.co.uk</a><br>------------------------------<br><br><br></div><br> = A quick guide to Mocha
<br><br> == Overview<br><br> Mocha is a framework for creating mock objects and stubbing methods of existing classes.<br><br> - Mocha lets users create mock objects and test expectations on those objects<br> - Mocha lets users stub methods on exiting objects (For a single object, for any
<br> object of a given class, or for singleton objects)<br> - Mocha is integrated with Ruby's<br> {Test::Unit}[<a onclick="return top.js.OpenExtLink(window,event,this)" href="http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html" target="_blank">
http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html</a>]<br> such that failed expectations with fail<br> {Test::Unit}[<a onclick="return top.js.OpenExtLink(window,event,this)" href="http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html" target="_blank">
http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html</a>]<br> tests, and stubs will be removed at the end of each test.<br><br> == Unit testing, mock objects and stubs<br><br> {Unit testing}[<a onclick="return top.js.OpenExtLink(window,event,this)" href="http://en.wikipedia.org/wiki/Unit_test" target="_blank">
http://en.wikipedia.org/wiki/Unit_test</a>] is a technique by which<br> you write tests for your code to make sure it works as expected, and future<br> additions do not break existing code. It is called "unit" testing because you
<br> only test a single unit of your own code (for instance just one class), and you<br> only test your own code. The problem is that code often has many dependencies,<br> and it is difficult to test it in isolation.<br><br>
There are two ways of dealing with this problem :<br> - <b>Mock objects</b> : substitute real objects with mock objects, and verify that<br> the object is being used as expected.<br> - <b>Stubs</b> : Replace specific methods of existing objects (including singleton
<br> objects) to return canned (pre-defined) data.<br><br> == Example with Mocha<br><br> === Mock objects<br><br> Say we are writting a class for logging error messages to a file, and we want to test<br> it. The class responds to two methods : 'output_to' which sets the IO object
<br> on which to ouput, and 'log' which takes a message to output :<br><br> class MyLogger<br> def output_to(file)<br> @file = file<br> end<br> def log(msg)<br> @file << msg<br> end<br>
end<br><br> The problem is the 'file' object - we don't want to use a real file, because it might<br> make the test fail even though our code is correct (for instance if we don't have<br> write access to the current directory). So we create a mock object to replace the
<br> file :<br><br> mockfile = mock('file')<br><br> At this point, we want to make sure that '<<' is called on our file object (This is<br> called, rightly, an expectation). This is done this way :<br><br> mockfile.expects
(:<<)<br><br> Here the call to 'expects' on the mock object does two things :<br> 1. It ensures that the test will succeed only if '<<' is called on mockfile<br> 2. It returns a Mocha::Expectation object<br><br>
The Mocha::Expectation objects can be used to further narrow what is expected<br> when '<<' is called. For instance if we want to make sure that the call to '<<'<br> gets the string 'some message' we can write :
<br><br> mockfile.expects(:<<).with('some message')<br><br> Calls to Mocha::Expectation object are chainable, so the expectation can be narrowed<br> further in the same line. For instance, we could add that '<<' must be called twice
<br> with 'some message' :<br><br> mockfile.expects(:<<).times(2).with('some message')<br><br> Mocha::Expectation also allows to define the behaviour of the mocked method - what it returns,<br> yields or raises. For instance in this case, '<<' should really return mockfile - or we might
<br> get into trouble since calls to '<<' can be chained :<br><br> mockfile.expects(:<<).times(2).with('some message').returns(mockfile)<br><br> We are now ready to write our test :<br><br> def test_output_to_file
<br> mockfile = mock('file')<br> mockfile.expects(:<<).times(2).with('some message').returns(mockfile)<br><br> MyLogger.output_to(mockfile)<br> MyLogger.log('some message')<br> MyLogger.log('some message')
<br> end<br><br> === Stubs<br><br> Next we want to be able to pass a file name directly to 'output_to', so we don't have to bother<br> opening the file ourselves. Our new MyLogger class looks like this :<br><br> class MyLogger
<br> def output_to(file)<br> if file.kind_of? String<br> @file = File.open(file, 'a+')<br> else<br> @file = file<br> end<br> end<br> def log(msg)<br> @file << msg<br>
end<br> end<br><br> And we want to test this new behaviour. Now we encounter a new problem : we can't use a mock<br> object to replace File, since we don't have the opportunity to pass that object to MyLogger -<br>
it uses it directly. So what we will do is replace (or "stub") the call to 'File.open' with our<br> own code. To make this as straightforward as possible, Mocha adds methods to the 'Object' class,<br> so we can write directly :
<br><br> File.expects(:open).with('test.txt', 'a+').returns(mockfile)<br><br> Again, we did two different things :<br> 1. We told File that it had to expect a call to 'open' with parameters 'test.txt' and 'a+'<br> (the test will fail otherwise)
<br> 2. We replaced 'File.open' shuch that it returns our mock object instead of a real IO object.<br><br> Our second test looks like this :<br><br> def test_output_to_named_file<br> # Create our mock IO object<br> mockfile = mock('file')
<br> mockfile.expects(:<<).with('some message').returns(mockfile)<br><br> # Stub File.open so that it returns our mock object<br> File.expects(:open).with('test.txt', 'a+').returns(mockfile)<br><br> # Run our code
<br> MyLogger.output_to('test.txt')<br> MyLogger.log('some message')<br> end<br><br> == The Mocha library<br><br> The library for mock objects is 'mocha.rb' and the library for stubs is 'stubba.rb'.<br> These should be loaded after Test::Unit :
<br> require 'test/unit'<br> require 'mocha'<br> require 'stubba'<br><br> Mocha provides three methods for creating mock objects, defined in the module<br> Mocha::AutoVerify :<br> - *mock* : creates a mock object for which expectations must be fullfilled
<br> - *stub* : creates a mock object for which expectations do not need to be fullfilled<br> - *stub_everything* : creates a mock object that accepts calls to any method<br><br> The objects created by those three methods respond to the three methods defined in
<br> Mocha::MockMethods :<br> - *expects* : Adds an expectation that a given method must be called. This returns a<br> Mocha::Expectation object<br> - *stubs* : Adds an expectation that a given method may be called. This returns a
<br> Mocha::Expectation object<br> - *verify* : Asserts that all expectations have been fulfilled. This is called<br> automatically if the mock object was created by one of the methods in Mocha::AutoVerify<br><br> See the API documentation of Mocha::Expectation for all the possible expectations
<br><br> == Mocha tips<br><br> === Code blocks for 'with' and 'returns'<br><br> The 'with' and 'returns' methods accept code blocks, which can be used for expecting or<br> returning different results when the same method is called several times. For instance
<br> the first test in <b>Example with Mocha</b> could test for different strings :<br><br> def test_output_to_file<br> mockfile = mock('file')<br> params = ["some message", "another message"]
<br> mockfile.expects(:<<).times(2).with { |p| p == params.shift}.returns(mockfile)<br><br> MyLogger.output_to(mockfile)<br> MyLogger.log('some message')<br> MyLogger.log('another message')<br> end
<br><br> === Shorthand mocks<br><br> Many mock objects really just return the same value for the same method. The methods for<br> creating mock objects in Mocha::AutoVerify provide a shortand notation for these :<br><br> access = mock({:username => 'joe', :password => 'ragrag'})
<br> access.username # Returns 'joe'<br> access.passowrd # Returns 'ragrag'<br><br><br><br><br>
<div>
<h1>A quick guide to Mocha</h1>
<h2>Overview</h2>
<p>
Mocha is a framework for creating mock objects and stubbing methods of
existing classes.
</p>
<ul>
<li>Mocha lets users create mock objects and test expectations on those objects
</li>
<li>Mocha lets users stub methods on exiting objects (For a single object, for
any object of a given class, or for singleton objects)
</li>
<li>Mocha is integrated with Ruby's <a href="http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html" target="_blank" onclick="return top.js.OpenExtLink(window,event,this)">Test::Unit</a>
such that failed expectations with fail <a href="http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html" target="_blank" onclick="return top.js.OpenExtLink(window,event,this)">Test::Unit</a>
tests, and stubs will be removed at the end of each test.
</li>
</ul>
<h2>Unit testing, mock objects and stubs</h2>
<p>
<a href="http://en.wikipedia.org/wiki/Unit_test" target="_blank" onclick="return top.js.OpenExtLink(window,event,this)">Unit testing</a> is a
technique by which you write tests for your code to make sure it works as
expected, and future additions do not break existing code. It is called
"unit" testing because you only test a single unit of your own
code (for instance just one class), and you only test your own code. The
problem is that code often has many dependencies, and it is difficult to
test it in isolation.
</p>
<p>
There are two ways of dealing with this problem :
</p>
<ul>
<li><b>Mock objects</b> : substitute real objects with mock objects, and verify
that the object is being used as expected.
</li>
<li><b>Stubs</b> : Replace specific methods of existing objects (including
singleton objects) to return canned (pre-defined) data.
</li>
</ul>
<h2>Example with Mocha</h2>
<h3>Mock objects</h3>
<p>
Say we are writting a class for logging error messages to a file, and we
want to test it. The class responds to two methods :
'output_to' which sets the IO object on which to ouput, and
'log' which takes a message to output :
</p>
<pre> class MyLogger<br> def output_to(file)<br> @file = file<br> end<br> def log(msg)<br> @file << msg<br> end<br> end<br></pre>
<p>
The problem is the 'file' object - we don't want to use a
real file, because it might make the test fail even though our code is
correct (for instance if we don't have write access to the current
directory). So we create a mock object to replace the file :
</p>
<pre> mockfile = mock('file')<br></pre>
<p>
At this point, we want to make sure that '<<' is called
on our file object (This is called, rightly, an expectation). This is done
this way :
</p>
<pre> mockfile.expects(:<<)<br></pre>
<p>
Here the call to 'expects' on the mock object does two things :
</p>
<ol>
<li>It ensures that the test will succeed only if '<<' is
called on mockfile
</li>
<li>It returns a Mocha::Expectation object
</li>
</ol>
<p>
The Mocha::Expectation objects can be used to further narrow what is
expected when '<<' is called. For instance if we want to
make sure that the call to '<<' gets the string
'some message' we can write :
</p>
<pre> mockfile.expects(:<<).with('some message')<br></pre>
<p>
Calls to Mocha::Expectation object are chainable, so the expectation can be
narrowed further in the same line. For instance, we could add that
'<<' must be called twice with 'some message'
:
</p>
<pre> mockfile.expects(:<<).times(2).with('some message')<br></pre>
<p>
Mocha::Expectation also allows to define the behaviour of the mocked method
- what it returns, yields or raises. For instance in this case,
'<<' should really return mockfile - or we might get into
trouble since calls to '<<' can be chained :
</p>
<pre> mockfile.expects(:<<).times(2).with('some message').returns(mockfile)<br></pre>
<p>
We are now ready to write our test :
</p>
<pre> def test_output_to_file<br> mockfile = mock('file')<br> mockfile.expects(:<<).times(2).with('some message').returns(mockfile)<br><br> MyLogger.output_to(mockfile)<br> MyLogger.log('some message')
<br> MyLogger.log('some message')<br> end<br></pre>
<h3>Stubs</h3>
<p>
Next we want to be able to pass a file name directly to
'output_to', so we don't have to bother opening the file
ourselves. Our new MyLogger class looks like this :
</p>
<pre> class MyLogger<br> def output_to(file)<br> if file.kind_of? String<br> @file = File.open(file, 'a+')<br> else<br> @file = file<br> end<br> end<br> def log(msg)<br> @file << msg
<br> end<br> end<br></pre>
<p>
And we want to test this new behaviour. Now we encounter a new problem : we
can't use a mock object to replace File, since we don't have
the opportunity to pass that object to MyLogger - it uses it directly. So
what we will do is replace (or "stub") the call to
'File.open' with our own code. To make this as straightforward
as possible, Mocha adds methods to the 'Object' class, so we
can write directly :
</p>
<pre> File.expects(:open).with('test.txt', 'a+').returns(mockfile)<br></pre>
<p>
Again, we did two different things :
</p>
<ol>
<li>We told File that it had to expect a call to 'open' with
parameters 'test.txt' and 'a+' (the test will fail
otherwise)
</li>
<li>We replaced 'File.open' shuch that it returns our mock object
instead of a real IO object.
</li>
</ol>
<p>
Our second test looks like this :
</p>
<pre> def test_output_to_named_file<br> # Create our mock IO object<br> mockfile = mock('file')<br> mockfile.expects(:<<).with('some message').returns(mockfile)<br><br> # Stub File.open so that it returns our mock object
<br> File.expects(:open).with('test.txt', 'a+').returns(mockfile)<br><br> # Run our code<br> MyLogger.output_to('test.txt')<br> MyLogger.log('some message')<br> end<br></pre>
<h2>The Mocha library</h2>
<p>
The library for mock objects is 'mocha.rb' and the library for
stubs is 'stubba.rb'. These should be loaded after Test::Unit :
</p>
<pre> require 'test/unit'<br> require 'mocha'<br> require 'stubba'<br></pre>
<p>
Mocha provides three methods for creating mock objects, defined in the
module Mocha::AutoVerify :
</p>
<ul>
<li><b>mock</b> : creates a mock object for which expectations must be
fullfilled
</li>
<li><b>stub</b> : creates a mock object for which expectations do not need to
be fullfilled
</li>
<li><b>stub_everything</b> : creates a mock object that accepts calls to any
method
</li>
</ul>
<p>
The objects created by those three methods respond to the three methods
defined in Mocha::MockMethods :
</p>
<ul>
<li><b>expects</b> : Adds an expectation that a given method must be called.
This returns a Mocha::Expectation object
</li>
<li><b>stubs</b> : Adds an expectation that a given method may be called. This
returns a Mocha::Expectation object
</li>
<li><b>verify</b> : Asserts that all expectations have been fulfilled. This is
called automatically if the mock object was created by one of the methods
in Mocha::AutoVerify
</li>
</ul>
<p>
See the API documentation of Mocha::Expectation for all the possible
expectations
</p>
<h2>Mocha tips</h2>
<h3>Code blocks for 'with' and 'returns'</h3>
<p>
The 'with' and 'returns' methods accept code
blocks, which can be used for expecting or returning different results when
the same method is called several times. For instance the first test in
<b>Example with Mocha</b> could test for different strings :
</p>
<pre> def test_output_to_file<br> mockfile = mock('file')<br> params = ["some message", "another message"]<br> mockfile.expects(:<<).times(2).with { |p| p == params.shift}.returns(mockfile)
<br><br> MyLogger.output_to(mockfile)<br> MyLogger.log('some message')<br> MyLogger.log('another message')<br> end<br></pre>
<h3>Shorthand mocks</h3>
<p>
Many mock objects really just return the same value for the same method.
The methods for creating mock objects in Mocha::AutoVerify provide a
shortand notation for these :
</p>
<pre> access = mock({:username => 'joe', :password => 'ragrag'})<br> access.username # Returns 'joe'<br> access.passowrd # Returns 'ragrag'<br></pre>
</div><br>_______________________________________________<br>mocha-developer mailing list<br><a onclick="return top.js.OpenExtLink(window,event,this)" href="mailto:mocha-developer@rubyforge.org">mocha-developer@rubyforge.org
</a><br><a onclick="return top.js.OpenExtLink(window,event,this)" href="http://rubyforge.org/mailman/listinfo/mocha-developer" target="_blank">http://rubyforge.org/mailman/listinfo/mocha-developer</a><br><br></blockquote>
</div><br><br clear="all"><br>-- <br>James.<br><a href="http://blog.floehopper.org">http://blog.floehopper.org</a>