Reputation: 5303
I have been getting into Unit Testing with Zend Framework. I am getting used to the other things it provide but I am having a hard time understanding Mock Objects.
For this example, I am trying to use a Mock Object to test out my model.
<?php
class Twitter_Model_Twitter
{
private $_twitter;
/**
* Make the options injectable.
* __contruct($auth, $key)
*/
public function __construct()
{
$config = new Zend_Config_Ini(APPLICATION_INI, APPLICATION_ENV);
$key = $config->encryption->salt;
$iv_size = mcrypt_get_iv_size(MCRYPT_XTEA, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$password = mcrypt_decrypt(MCRYPT_XTEA, $key, $password, MCRYPT_MODE_ECB, $iv);
$this->_twitter = new Zend_Service_Twitter($username, $password);
}
public function verifyCredentials()
{
return $this->_twitter->account->verifyCredentials();
}
public function friendsTimeline($params)
{
return $this->_twitter->status->friendsTimeline($params);
}
}
For my unit test:
require_once ('../application/models/Twitter.php');
class Model_TwitterTest extends ControllerTestCase
{
/**
* @var Model_Twitter
*/
protected $_twitter;
public function testfriendsTimeline()
{
$mockPosts = array('foo', 'bar');
//my understanding below is:
//get a mock of Zend_Service_Twitter with the friendsTimeline method
$twitterMock = $this->getMock('Zend_Service_Twitter', array('friendsTimeline'));
/*
line above will spit out an error:
1) testfriendsTimeline(Model_TwitterTest)
Missing argument 1 for Mock_Zend_Service_Twitter_9fe2aeaa::__construct(), called in
/Applications/MAMP/bin/php5/lib/php/PHPUnit/Framework/TestCase.php on line 672 and
defined /htdocs/twitter/tests/application/models/TwitterTest.php:38
*/
$twitterMock->expects($this->once())
->method('friendsTimeline')
->will($this->returnValue($mockPosts));
$model = new Twitter_Model_Twitter();
$model->setOption('twitter', $twitterMock);
$posts = $model->friendsTimeline(array('count'=>20));
$this->assertEquals($posts, $mockPosts);
}
}
How would you test the following? 1) verifyCredentials() 2) friendsTimeline()
Thanks, Wenbert
Upvotes: 4
Views: 3848
Reputation: 5303
I am going to answer this question. I think I have made this work thanks to zomg from #zftalk.
Here is my new Twitter Model:
<?php
//application/models/Twitter.php
class Twitter_Model_Twitter
{
private $_twitter;
private $_username;
private $_password;
public function __construct(array $options = null)
{
if (is_array($options)) {
$this->setOptions($options);
$this->_twitter = new Zend_Service_Twitter($this->_username, $this->_password);
} else {
$twitterAuth = new Zend_Session_Namespace('Twitter_Auth');
$config = new Zend_Config_Ini(APPLICATION_INI, APPLICATION_ENV);
$key = $config->encryption->salt;
$iv_size = mcrypt_get_iv_size(MCRYPT_XTEA, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$password = mcrypt_decrypt(MCRYPT_XTEA, $key, $twitterAuth->password, MCRYPT_MODE_ECB, $iv);
$username = $twitterAuth->username;
$this->_twitter = new Zend_Service_Twitter($username, $password);
}
}
public function setOptions(array $options)
{
$methods = get_class_methods($this);
foreach ($options as $key => $value) {
$pieces = explode('_', $key);
foreach($pieces AS $piece_key => $piece_value) {
$pieces[$piece_key] = ucfirst($piece_value);
}
$name = implode('',$pieces);
$method = 'set' . $name;
//$method = 'set' . ucfirst($key);
if (in_array($method, $methods)) {
$this->$method($value);
}
}
return $this;
}
//I added this method. So that I could "inject"/set the $_twitter obj
public function setTwitter($obj)
{
$this->_twitter = $obj;
return $this;
}
public function verifyCredentials()
{
return $this->_twitter->account->verifyCredentials();
}
public function friendsTimeline($params)
{
return $this->_twitter->status->friendsTimeline($params);
}
//in the real code, more will go here...
}
And in my Unit Test, I have this:
<?php
// tests/application/models/TwitterTest.php
require_once ('../application/models/Twitter.php');
class Model_TwitterTest extends ControllerTestCase
{
public function testVerifyCredentials()
{
$stub = $this->getMock('Zend_Service_Twitter', array('verifyCredentials'),array(),'',FALSE);
//FALSE is actually the 5th parameter to flag getMock not to call the main class. See Docs for this.
//Now that I have set the $_twitter variable to use the mock, it will not call the main class - Zend_Rest_Client (i think)
$stub->expects($this->once())
->method('verifyCredentials');
$model = new Twitter_Model_Twitter();
//this is the part where i set the $_twitter variable in my model to use the $stub
$model->setOptions(array('twitter'=>$stub));
$model->verifyCredentials();
}
}
Anyways, I think I got it working.
1) The unit test no longer tried to connect to twitter.com:80
2) After I got the setOptions() working in the Twitter_Model, $model->verifyCredentials() in my unit test was successfully called.
I will wait for others in Stackoverflow to confirm that is the right answer. For the meantime, would like to hear from you guys.
Thanks!!!
Upvotes: 1