Hi Anselm,
Thanks - that''s really helpful - I''ll take a look :-)
James.
On 08/09/06, Anselm Heaton <anselm at netuxo.co.uk>
wrote:>
> Hello :)
>
> Having just gone through the (admittedly simple) API of Mocha to learn
> how it works, I wrote a quick guide (190 lines) to help others get up to
> speed. I''ve attached it as RDOC and as HTML.
>
> It is a first draft - please help me improve by giving feedback !
>
> Contents :
> Overview - Quick overview of Mocha
> Unit testing, mock object and stubs - Very quick introduction to mock
> objects and stubs
> Example with Mocha - The core of the guide ; shows how to use Mocha with
> a continuing example
> The Mocha library - How the library is structured
> Mocha tips - Some tips I would''ve found useful at first :)
>
> Best,
> Anselm
>
> --
> ------------------------------
> Netuxo Ltd
> a workers'' co-operative
> providing low-cost IT solutions
> for peace, environmental and social justice groups
> and the radical NGO sector
>
> Registered as a company in England and Wales. No 4798478
> Registered office: 5 Caledonian Road, London N1 9DY, Britain
> ------------------------------
> email office at netuxo.co.uk
> http://www.netuxo.co.uk
> ------------------------------
>
>
>
> = A quick guide to Mocha
>
> == Overview
>
> Mocha is a framework for creating mock objects and stubbing methods of
> existing classes.
>
> - Mocha lets users create mock objects and test expectations on those
> objects
> - Mocha lets users stub methods on exiting objects (For a single object,
> for any
> object of a given class, or for singleton objects)
> - Mocha is integrated with Ruby''s
> {Test::Unit}[
> http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html
> ]
> such that failed expectations with fail
> {Test::Unit}[
> http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html
> ]
> tests, and stubs will be removed at the end of each test.
>
> == Unit testing, mock objects and stubs
>
> {Unit testing}[http://en.wikipedia.org/wiki/Unit_test] 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.
>
> There are two ways of dealing with this problem :
> - <b>Mock objects</b> : substitute real objects with mock
objects, and
> verify that
> the object is being used as expected.
> - <b>Stubs</b> : Replace specific methods of existing objects
(including
> singleton
> objects) to return canned (pre-defined) data.
>
> == Example with Mocha
>
> === Mock objects
>
> 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 :
>
> class MyLogger
> def output_to(file)
> @file = file
> end
> def log(msg)
> @file << msg
> end
> end
>
> 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 :
>
> mockfile = mock(''file'')
>
> 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 :
>
> mockfile.expects(:<<)
>
> Here the call to ''expects'' on the mock object does two
things :
> 1. It ensures that the test will succeed only if
''<<'' is called on
> mockfile
> 2. It returns a Mocha::Expectation object
>
> 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 :
>
> mockfile.expects(:<<).with(''some message'')
>
> 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'' :
>
> mockfile.expects(:<<).times(2).with(''some
message'')
>
> 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 :
>
> mockfile.expects(:<<).times(2).with(''some
message'').returns(mockfile)
>
> We are now ready to write our test :
>
> def test_output_to_file
> mockfile = mock(''file'')
> mockfile.expects(:<<).times(2).with(''some
message'').returns(mockfile)
>
> MyLogger.output_to(mockfile)
> MyLogger.log(''some message'')
> MyLogger.log(''some message'')
> end
>
> === Stubs
>
> 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 :
>
> class MyLogger
> def output_to(file)
> if file.kind_of? String
> @file = File.open(file, ''a+'')
> else
> @file = file
> end
> end
> def log(msg)
> @file << msg
> end
> end
>
> 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 :
>
> File.expects(:open).with(''test.txt'',
''a+'').returns(mockfile)
>
> Again, we did two different things :
> 1. We told File that it had to expect a call to ''open''
with parameters ''
> test.txt'' and ''a+''
> (the test will fail otherwise)
> 2. We replaced ''File.open'' shuch that it returns our
mock object instead
> of a real IO object.
>
> Our second test looks like this :
>
> def test_output_to_named_file
> # Create our mock IO object
> mockfile = mock(''file'')
> mockfile.expects(:<<).with(''some
message'').returns(mockfile)
>
> # Stub File.open so that it returns our mock object
> File.expects(:open).with(''test.txt'',
''a+'').returns(mockfile)
>
> # Run our code
> MyLogger.output_to(''test.txt'')
> MyLogger.log(''some message'')
> end
>
> == The Mocha library
>
> The library for mock objects is ''mocha.rb'' and the
library for stubs is ''
> stubba.rb''.
> These should be loaded after Test::Unit :
> require ''test/unit''
> require ''mocha''
> require ''stubba''
>
> Mocha provides three methods for creating mock objects, defined in the
> module
> Mocha::AutoVerify :
> - *mock* : creates a mock object for which expectations must be
> fullfilled
> - *stub* : creates a mock object for which expectations do not need to be
> fullfilled
> - *stub_everything* : creates a mock object that accepts calls to any
> method
>
> The objects created by those three methods respond to the three methods
> defined in
> Mocha::MockMethods :
> - *expects* : Adds an expectation that a given method must be called.
> This returns a
> Mocha::Expectation object
> - *stubs* : Adds an expectation that a given method may be called. This
> returns a
> Mocha::Expectation object
> - *verify* : 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
>
> See the API documentation of Mocha::Expectation for all the possible
> expectations
>
> == Mocha tips
>
> === Code blocks for ''with'' and
''returns''
>
> 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 :
>
> def test_output_to_file
> mockfile = mock(''file'')
> params = ["some message", "another message"]
> mockfile.expects(:<<).times(2).with { |p| p == params.shift
> }.returns(mockfile)
>
> MyLogger.output_to(mockfile)
> MyLogger.log(''some message'')
> MyLogger.log(''another message'')
> end
>
> === Shorthand mocks
>
> 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 :
>
> access = mock({:username => ''joe'', :password =>
''ragrag''})
> access.username # Returns ''joe''
> access.passowrd # Returns ''ragrag''
>
>
>
>
> A quick guide to Mocha Overview
>
> Mocha is a framework for creating mock objects and stubbing methods of
> existing classes.
>
> - Mocha lets users create mock objects and test expectations on
> those objects
> - Mocha lets users stub methods on exiting objects (For a single
> object, for any object of a given class, or for singleton objects)
> - Mocha is integrated with Ruby''s
Test::Unit<http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html>such
that failed expectations with fail
>
Test::Unit<http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html>tests,
and stubs will be removed at the end of each test.
>
> Unit testing, mock objects and stubs
>
> Unit testing <http://en.wikipedia.org/wiki/Unit_test> 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.
>
> There are two ways of dealing with this problem :
>
> - *Mock objects* : substitute real objects with mock objects, and
> verify that the object is being used as expected.
> - *Stubs* : Replace specific methods of existing objects (including
> singleton objects) to return canned (pre-defined) data.
>
> Example with Mocha Mock objects
>
> 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 :
>
>
> class MyLogger
> def output_to(file)
> @file = file
> end
> def log(msg)
> @file << msg
> end
> end
>
> 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 :
>
> mockfile = mock(''file'')
>
> 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 :
>
> mockfile.expects(:<<)
>
> Here the call to ''expects'' on the mock object does two
things :
>
> 1. It ensures that the test will succeed only if
''<<'' is called on
> mockfile
> 2. It returns a Mocha::Expectation object
>
> 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 :
>
> mockfile.expects(:<<).with(''some message'')
>
> 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'' :
>
> mockfile.expects(:<<).times(2).with(''some
message'')
>
> 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 :
>
> mockfile.expects(:<<).times(2).with(''some
message'').returns(mockfile)
>
> We are now ready to write our test :
>
> def test_output_to_file
> mockfile = mock(''file'')
> mockfile.expects(:<<).times(2).with(''some
message'').returns(mockfile)
>
> MyLogger.output_to(mockfile)
> MyLogger.log(''some message'')
> MyLogger.log(''some message'')
> end
>
> Stubs
>
> 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 :
>
> class MyLogger
> def output_to(file)
> if file.kind_of? String
> @file = File.open(file, ''a+'')
> else
> @file = file
> end
> end
> def log(msg)
> @file << msg
> end
> end
>
> 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 :
>
> File.expects(:open).with(''test.txt'',
''a+'').returns(mockfile)
>
> Again, we did two different things :
>
> 1. We told File that it had to expect a call to ''open''
with
> parameters ''test.txt'' and ''a+'' (the
test will fail otherwise)
> 2. We replaced ''File.open'' shuch that it returns our
mock object
> instead of a real IO object.
>
> Our second test looks like this :
>
> def test_output_to_named_file
> # Create our mock IO object
> mockfile = mock(''file'')
> mockfile.expects(:<<).with(''some
message'').returns(mockfile)
>
> # Stub File.open so that it returns our mock object
> File.expects(:open).with(''test.txt'',
''a+'').returns(mockfile)
>
> # Run our code
> MyLogger.output_to(''test.txt'')
> MyLogger.log(''some message'')
> end
>
> The Mocha library
>
> The library for mock objects is ''mocha.rb'' and the
library for stubs is
> ''stubba.rb''. These should be loaded after Test::Unit :
>
> require ''test/unit''
> require ''mocha''
> require ''stubba''
>
> Mocha provides three methods for creating mock objects, defined in the
> module Mocha::AutoVerify :
>
> - *mock* : creates a mock object for which expectations must be
> fullfilled
> - *stub* : creates a mock object for which expectations do not need
> to be fullfilled
> - *stub_everything* : creates a mock object that accepts calls to
> any method
>
> The objects created by those three methods respond to the three methods
> defined in Mocha::MockMethods :
>
> - *expects* : Adds an expectation that a given method must be
> called. This returns a Mocha::Expectation object
> - *stubs* : Adds an expectation that a given method may be called.
> This returns a Mocha::Expectation object
> - *verify* : 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
>
> See the API documentation of Mocha::Expectation for all the possible
> expectations
> Mocha tips Code blocks for ''with'' and
''returns''
>
> 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 *Example with Mocha* could
> test for different strings :
>
> def test_output_to_file
> mockfile = mock(''file'')
> params = ["some message", "another message"]
> mockfile.expects(:<<).times(2).with { |p| p ==
params.shift}.returns(mockfile)
>
> MyLogger.output_to(mockfile)
> MyLogger.log(''some message'')
> MyLogger.log(''another message'')
> end
>
> Shorthand mocks
>
> 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 :
>
> access = mock({:username => ''joe'', :password =>
''ragrag''})
> access.username # Returns ''joe''
> access.passowrd # Returns ''ragrag''
>
>
> _______________________________________________
> mocha-developer mailing list
> mocha-developer at rubyforge.org
> http://rubyforge.org/mailman/listinfo/mocha-developer
>
>
--
James.
http://blog.floehopper.org
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
http://rubyforge.org/pipermail/mocha-developer/attachments/20060909/6bc0bf22/attachment-0001.html