![]() |
|
sfLimeExtraPlugin - 0.2.1Mock and annotation support for lime |
|
![]() |
2
users
Sign-in
to change your status |
Mock and annotation support for lime |
DISCONTINUED! PLEASE USE LIME 2 INSTEAD.
http://svn.symfony-project.com/tools/lime/2.0
| Name | Status | |
|---|---|---|
|
|
lead | moc.liamg <<ta>> kessuhcsb |
Copyright (c) 2009 Bernhard Schussek
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
| Version | License | API | Released |
|---|---|---|---|
| 0.2.1alpha | MIT license | 0.2.1alpha | 30/06/2009 |
| 0.2.0alpha | MIT license | 0.2.0alpha | 28/06/2009 |
| 0.1.1alpha | MIT license | 0.1.1alpha | 25/06/2009 |
| Version | License | API | Released |
|---|---|---|---|
| 0.2.1alpha | MIT license | 0.2.1alpha | 30/06/2009 |
| 0.2.0alpha | MIT license | 0.2.0alpha | 28/06/2009 |
| 0.1.1alpha | MIT license | 0.1.1alpha | 25/06/2009 |
Added support for annotated test scripts. See the inline documentation of class lime_test_simple for details.
Alpha developer preview.
Note: The readme currently does not explain the possibilities of annotated tests. For more information about that, I recommend to read my blog post over at Web Mozarts.
Stubs and mock objects can simulate the behaviour of real classes. They are useful for testing code that depends on third classes (the "dependent-on component"). For the purpose of the test it is mostly sufficient though to provide a fake object that behaves as if it was the real class without ever executing the real class's code.
sfLimeExtraPlugin provides a light-weight and intuitive mocking framework for use with lime.
Attention: This plugin is still in its alpha stages and subject to change. I would be happy if you want to try it out to give me feedback.
The plugin can be obtained by checking it out from SVN:
svn co http://svn.symfony-project.com/plugins/sfLimeExtraPlugin/trunk
As soon as the plugin is installed, it is ready to use.
Stubs are objects that offer the same methods as the stubbed class but usually return fixed values for the purpose of a test. Let's start with the following class as example:
class User { protected $storage; public function User(SessionStorage $storage) { $this->storage = $storage; } public function setAttribute($name, $value) { $this->storage->write($name, $value); $this->storage->close(); } public function getAttribute($name) { return $this->storage->read($name); } }
Let's look at a unit test for setAttribute():
$t->comment('Attributes can be read from the session'); // fixture $stub = new SessionStorageDatabase(); $stub->write('Foo', 'Bar'); $stub->close(); $u = new User($stub); // test $value = $u->getAttribute('Foo'); // assertions $t->is($value, 'Bar', 'The value has been read');
Now SessionStorage might require access to the file system or access to a
database, as in our case. Accessing a database everytime you test your User
class will heavily affect the speed of your tests. Thus the option is to replace
SessionStorage with a fake implementation (a stub).
class StubSessionStorage implements SessionStorage { private $name; private $value; public function __construct($name, $value) { $this->name = $name; $this->value = $value; } public function read($name) { return $name == $this->name ? $this->value : null; } public function write($name, $value) {} public function close() {} }
Let's adapt our test to use the StubSessionStorage:
$t->comment('Attributes can be read from the session'); // fixture $u = new User(new StubSessionStorage('Foo', 'Bar')); // test $value = $u->getAttribute('Foo'); // assertions $t->is($value, 'Bar', 'The value has been read');
As you can see, our test just got a lot easier to read. The drawback is that we had to invest a lot of code into writing our stub class. Usually it is worth the effort though, because the stub class can be reused in other tests. Especially in the long term, a good test readability and maintainability pays off.
Note how we injected the name and the value of the expected parameter into
our stub class. This means again more code in the stub class, but in the test
it is very clear why getAttribute() should return 'Bar' when called
with the argument 'Foo'.
sfLimeExtraPlugin provides you with facilities to create stubs a lot easier than by having to write your own stub classes. With sfLimeExtraPlugin, we can change the test above code to:
$t->comment('Attributes can be read from the session'); // fixture $stub = lime_mock::create('SessionStorage'); $stub->read('Foo')->returns('Bar'); $stub->replay(); $u = new User($stub); // test $value = $u->getAttribute('Foo'); // assertions $t->is($value, 'Bar', 'The value has been read');
As you can see, we just create the stub by calling lime_mock::create() with
the stubbed class or interface name. Then we "record" the methods that should
be called with what parameters and their return values. In the end we switch
the storage to "replay" mode. In this mode, the methods that we just configured
will return the desired return values, when called with the right arguments.
Sometimes it is not enough to replace a dependent-on component by setting up static method return values. In some cases you will also want to test whether the dependent-on component receives the right data and method calls.
For this purpose we will extend our last example a bit. We don't only want to test whether the user can read attributes from the session, but we also want to test whether attributes are written correctly into the session.
$t->comment('Attributes can be stored permanently'); // fixture $mock = lime_mock::create('SessionStorage', $t); $mock->write('Foo', 'Bar'); $mock->close(); $mock->replay(); $u = new User($mock); // test $u->setAttribute('Foo', 'Bar'); // assertions $mock->verify();
In this example, the new concept of verification comes into play. After
executing your test code you can call verify() on the mock object to verify
whether all the expected methods have been called with the right parameters.
If any of the methods would not have been called, verify() would result
in a failed test.
This section covers the capabilities of the mock objects. It describes which methods you have to call to configure the mock for your needs.
returns()Configures a method to return a specific value.
$mock->doSomething()->returns('Foobar'); $mock->replay(); echo $mock->doSomething(); // prints 'Foobar'
throws()Configures a method to throw a specific exception.
$mock->doSomething()->throws('InvalidArgumentException'); $mock->replay(); $mock->doSomething(); // throws an InvalidArgumentException
times()Configures a method to be called a specific number of times.
$mock->doSomething()->times(2); $mock->replay(); $mock->doSomething(); $mock->verify(); // results in a failed test
setFailOnVerify()If an unexpected method is called or if a method is called to often, a
lime_expectation_exception is thrown.
$mock->doSomething()->times(2); $mock->replay(); $mock->doSomething(); $mock->doSomething(); $mock->doSomething(); // throws a lime_expectation_exception
This is very useful for discovering where the unexpected call resulted from. You
can suppress this behaviour though by calling setFailOnVerify(), so that
your code will only be validated when verify() is called.
$mock->setFailOnVerify(); $mock->doSomething()->times(2); $mock->replay(); $mock->doSomething(); $mock->doSomething(); $mock->doSomething(); $mock->verify(); // results in a failed test
setExpectNothing()By default, all method calls are ignored if you did not set up any expected methods.
$mock->replay(); $mock->doSomething(); // ignored $mock->verify(); // results in a passed test
You can configure the mock though to verify that exactly no method has been called:
$mock->setExpectNothing(); $mock->replay(); $mock->doSomething(); // throws a lime_expectation_exception
For each mock object, the following methods are generated automatically:
verify()replay()setExpectNothingsetStrict()setFailOnVerify()These methods allow for a very comfortable usage. The drawback is that you cannot mock methods with the same name in your class.
$mock = lime_mock::create('MusicPlayer', $t); $mock->replay()->returns('...'); // does not work
The solution is, to set the third parameter $generateControls to false
when calling create(). When you do that, you will have to call the above
methods statically in lime_mock with the mock object as first argument.
$mock = lime_mock::create('MusicPlayer', $t, false); $mock->replay()->returns('Foobar'); lime_mock::replay($mock); echo $mock->replay(); // prints 'Foobar' lime_mock::verify($mock); // results in a successful test
see LICENSE file
