I am starting to write phpUnit test and faced with such problem. 80% of my functions ending on such lines
$data["res"] = $this->get_some_html($this->some_id);
echo my_json_encode($data);
return true;
How can i make test on such kind of functions in my classes?
You need to isolate your code into testable 'chunks'. You can test that the function returns TRUE/FALSE given specified text, and then test the JSON return data given fixed information.
function my_json_encode($data)
{
return ...;
}
function get_some_html($element)
{
return ...;
}
function element_exists($element)
{
return ..;
}
function display_data($element)
{
if(element_exists($element)
{
$data = get_some_html($element);
$json = my_json_encode($data);
return true;
}
else
{
return false;
}
}
Testing:
public function test_my_json_encode()
{
$this->assertEquals($expected_encoded_data, my_json_encode($text));
}
public function test_get_some_html()
{
$this->assertEquals($expected_html, get_some_html('ExistingElementId'));
}
public function test_element_exists()
{
$this->assertTrue(element_exists('ExistingElementId');
$this->assertFalse(element_exists('NonExistingElementId');
}
function test_display_data()
{
$this->assertTrue(display_data('ExistingElementId'));
$this->assertFalse(element_exists('NonExistingElementId');
}
This is a simple, abstract example of the changes and the testing. As the comments above have indicated, you might want to change the return to be the JSON text, and a FALSE on error, then use === testing in your code to decide to display the text or not.
The next step would be to mock out the Elements, so you can get expected data without the need for a real HTML page.
Related
I'm trying to learn how to do unit testing in general but specifically the project I'm working on is built with CakePHP. I have this parentNode() method in my user model taken directly from the Simple Acl Controlled Application tutorial.
public function parentNode() {
if (!$this->id && empty($this->data)) {
return null;
}
if (isset($this->data['User']['group_id'])) {
$groupId = $this->data['User']['group_id'];
} else {
$groupId = $this->field('group_id');
}
if (!$groupId) {
return null; // not tested
} else {
return array('Group' => array('id' => $groupId));
}
}
I wrote the following tests
public function testParentNodeHasNoUserDataOrId() {
unset($this->User->id);
unset($this->User->data);
$this->assertNull($this->User->parentNode());
}
public function testParentNodeWithGroupIDInUserData() {
$this->User->data['User']['group_id'] = 1;
$this->assertEquals(array('Group'=>array('id'=>1)),$this->User->parentNode());
}
public function testParentNodeWithoutGroupIDInUserData() {
$this->User->id = 1;
unset($this->User->data['User']['group_id']);
$this->assertEquals(array('Group'=>array('id'=>1)), $this->User->parentNode());
}
and they all seem to work. My code coverage report however shows that I'm not testing the return null; in the if(!$groupId) block. I can't figure out how to test that line.
As far as I can tell it will never execute. If my User model has no id and no data it returns null in the first if block. if I cheat a bit and give the user some fake data $this->field('group_id') is still returning group 1 (I think it should return false instead but it doesn't)
So when unit testing do you have to test everything? How could I test for the return null if(!$groupId)? If there's code that will never execute should I just remove it?
Thanks!
I figured out how I could test for the "impossible" scenario with the following test
public function testParentNodeWhenUserHasNoGroupId() {
$this->User->id = 1;
$user = $this->User->read();
$user['User']['group_id'] = 0;
$this->User->save($user);
$this->assertNull($this->User->parentNode());
}
I have a function that takes an input variable and outputs a template with the following call:
outputhtml($blue_widget);
outputhtml($red_widget);
outputhtml($green_widget);
And a simplified version of the function:
function outputhtml($type)
{
static $current;
if (isset($current))
{
$current++;
}
else
{
$current = 0;
}
//some logic here to determine template to output
return $widget_template;
}
Now here is my problem. If I call the function in a script three times or more, I want the output to be one way, but if I only call the function twice, then I have some html changes that need to be reflected in the templates that are returned.
So how can I modify this function to determine if there are only two calls for it. I can't go back after the fact and ask "hey function did you only run twice???"
Having trouble getting my head around how I tell a function that it is not going to be used after the second time and the necessary html modifications can be used. How would I go about accomplishing this?
function outputhtml($type)
{
static $current = 0;
$current++;
//some logic here to determine template to output
if ($current === 2) {
// called twice
}
if ($current > 2) {
// called more than twice
}
return $widget_template;
}
That would not be practical using a static $current inside the function; I would suggest using an object to maintain the state instead, like so:
class Something
{
private $current = 0;
function outputhtml($type)
{
// ... whatever
++$this->current;
return $template;
}
function didRunTwice()
{
return $this->current == 2;
}
}
The didRunTwice() method is asking "did you run twice?".
$s = new Something;
$tpl = $s->outputhtml(1);
// some other code here
$tpl2 = $s->outputhtml(2);
// some other code here
if ($s->didRunTwice()) {
// do stuff with $tpl and $tpl2
}
The only way you can find out if a function was only called twice is by putting the test at the end of your code; but perhaps by then the templates are no longer accessible? Can't tell much without seeing more code.
I've an xml file containing tag like this.
<server>
<conversation ip="12.0.0.1" email="none">
<chat userstatus="1" adminstatus="2" username="admin">muja</chat>
</conversation>
</server>
Now I want to update the email attribute of the conversation tag.
When I use $conv->getAttribute("email") it echo's me the correct result i.e none.But if I try to set it using $conv->setAttribute("email","abc") it does not update the value.
Here's what I am doing.
This is the GetClientConversation():
private function GetClientConversation()
{
foreach($this->conversation as $convTag)
{
if($convTag->getAttribute("ip") == $this->clientip)
{
return $convTag;
}
}
return "noConversation";
}
This function returns me the correct conversationTag that I needed.
And i get these conversationsTags array using
$this->conversation=$this->xmlDom->getElementsByTagName("conversation");
Edit:
public function GetConversation()
{
$conv=$this->GetClientConversation();
if($conv!="noConversation")
{
if($conv->getAttribute("email")=="none")
{
$conv->setAttribute("email","abc"); // -- Here
return json_encode($this->RetrieveConversation($conv));
}
else if($conv->getAttribute("email")==$this->adminEmail)
{
return json_encode($this->RetrieveConversation($conv));
}
else
{
return "Admin Already Chatting";
}
}
else
{
$this->CreateNewConversation();
return "no";
}
}
This is the code from where I am trying to set the attribute.
You have correctly used setAttribute().
You are retrieving your XML and passing the string back to json_encode(). However, if the RetrieveConversation() method has not correctly called saveXML() prior to returning the string, your modifications will not be available in the output XML string. Be sure you have called saveXML().
Inherited an old CakePHP site and I'm trying to figure out what some functions do. I have several functions that have the same name as another function but with an underscore first, e.g. save() and _save(). However the function _save() is never called in any context, though save() is.
I read this question and it looks like it's from an old worst-practices exercise, but that doesn't really explain why it's in my code; you still have to call function _save() as _save() right? If there's no calls to _save() is it safe to remove?
I want it gone, even the save() function wasn't supposed to be there, rewriting perfectly good framework functionality. It looks like an older version of the same function, but there's no comments and I don't know if there's some weird context in which php/Cake will fall back to the underscored function name.
Here's the code for the curious. On closer inspection it appears the underscored functions were old versions of a function left in for some reason. At least one was a "private" method being called (from a public function of the same name, minus the underscore...):
function __save() {
$user = $this->redirectWithoutPermission('product.manage','/',true);
if ($this->data) {
$this->Prod->data = $this->data;
$saved_okay = false;
if ($this->Prod->validates()) {
if ($this->Prod->save()) $saved_okay = true;
}
if ($saved_okay) {
$product_id = ($this->data['Prod']['id']) ? $this->data['Prod']['id'] : $this->Prod->getLastInsertId();
if ($this->data['Plant']['id']) {
$this->data['Prod']['id'] = $product_id;
$this->Prod->data = $this->data;
$this->Prod->save_plants();
$this->redirect('/plant/products/'.$this->data['Plant']['id']);
} else {
$this->redirect('/product/view/'.$product_id);
}
die();
} else {
die('did not save properly');
}
} else {
die('whoops');
}
}
function save() {
$user = $this->redirectWithoutPermission('product.manage','/products',true);
if ($this->data) {
$this->Prod->data = $this->data;
if ($this->Prod->validates()) {
$this->Prod->save();
$gotoURL = isset($this->data['Navigation']['goto'])?$this->data['Navigation']['goto']:'/';
$gotoURL = str_replace('%%Prod.id%%', $this->data['Prod']['id'], $gotoURL);
if (isset($this->data['Navigation']['flash'])) {
$this->Session->setFlash($this->data['Navigation']['flash']);
}
if (isset($this->params['url']['ext']) && $this->params['url']['ext']=='ajax') {
$value = array(
'success'=>true
,'redirect'=>$gotoURL
);
print $this->Json->encode($value);
} else {
$this->redirect($gotoURL);
}
} else {
$value = array(
'success'=>false
,'message'=>"You have invalid fields."
,'reason'=>'invalid_fields'
,'fields'=>array(
'Prod'=>$this->Prod->invalidFields()
)
);
print $this->Json->encode($value);
}
} else {
$this->redirect('/products');
}
die();
}
I had hoped to learn whether or not some convention applied to this situation, but from testing I've found the functions are not called which is really the answer to the question I asked.
I'm working on creating a callback function in codeigniter to see if a certain record exists in the database, and if it does it'd like it to return a failure.
In the controller the relevent code is:
function firstname_check($str)
{
if($this->home_model->find_username($str)) return false;
true;
}
Then in the model I check the database using the find_username() function.
function find_username($str)
{
if($this->db->get_where('MasterDB', array('firstname' => $str)))
{
return TRUE;
}
return FALSE;
}
I've used the firstname_check function in testing and it works. I did something like
function firstname_check($str)
{
if($str == 'test') return false;
true;
}
And in that case it worked. Not really sure why my model function isn't doing what it should. And guidance would be appreciated.
if($this->home_model->find_username($str)) return false;
true;
Given that code snippet above, you are not returning it true. If that is your code and not a typo it should be:
if($this->home_model->find_username($str)) return false;
return true;
That should fix it, giving that you did not have a typo.
EDIT:
You could also just do this since the function returns true/false there is no need for the if statement:
function firstname_check($str)
{
return $this->home_model->find_username($str);
}
So the solution involved taking the query statement out of if statement, placing it into a var then counting the rows and if the rows was > 0, invalidate.
Although this is a more convoluted than I'd like.
I find your naming kind of confusing. Your model function is called 'find_username' but it searches for a first name. Your table name is called 'MasterDB'. This sounds more like a database name. Shouldn't it be called 'users' or something similar? I'd write it like this :
Model function :
function user_exists_with_firstname($firstname)
{
$sql = 'select count(*) as user_count
from users
where firstname=?';
$result = $this->db->query($sql, array($firstname))->result();
return ((int) $result->user_count) > 0;
}
Validation callback function :
function firstname_check($firstname)
{
return !$this->user_model->user_exists_with_firstname($firstname);
}