I've been working on getting our systems more compatible with PHPUnit so we can do more unit testing of our classes and have managed to get some of them working with mock objects, but I've run across a problem which I can't seem to get around.
One of the classes we have (which I'm creating a mock version of) is for queries. You pass the query string into it's "query()" method, it logs the query, runs it and returns the result. It also wraps the mysql_fetch_assoc with a method called "get_row()", which returns an array value much like the original.
The problem is that, in some methods, there's more than one query being passed to the "query()" method and as a result it needs to run through multiple while loops to load the data into different variables. I've created a simplified version below:
class object{
public function __construct($query){
$this->query = $query;
}
public function loadData(){
$data1 = queryDataSource("SELECT * FROM data1");
$data2 = queryDataSource("SELECT * FROM data2");
return Array(
"data1" => $data1,
"data2" => $data2,
);
}
private function queryDataSource($query){
$this->query->query($query)
while($row = $this->query->get_row()){
$result[] = $row;
}
return $result
}
}
class testObject extends PHPUnit_Framework_TestCase{
method testLoadData(){
$test_data = Array('name' => 'Bob', 'number' => '98210');
$query = $this->getMock('Query');
$query->expects($this->any())->method('query');
$query->expects($this->at(1))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(2))->method('get_row')->will($this->returnValue(False);
$query->expects($this->at(3))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(4))->method('get_row')->will($this->returnValue(False);
}
}
In order to escape the first while loop in $object->queryDataSource() I'm returning a boolean FALSE value, as would happen when doing mysql_fetch_assoc. The problem is that, when it tries to run the second query and fetch the data through get_row(), the mock object seems to keep returning FALSE ratehr than moving on to the at(3) point. This happens even with 4 objects, only the first will get the test data as a return value then get FALSE the second time, the others will get FALSE every time.
Does anyone know if there's a way to get around this? I tried removing the FALSE flags and just having the odd values in at(), but that had the same problem, and I tried just having it return the data for at(1-2), but that just passed all the data into the first while loop and nothing for the other.
Thanks for any help you can give, hope the description of the problem's clear enough
I can't run the code as it seems to only be pseudocode but from what I understood is that you are trying to mock like this:
Call to query, get_row, get_row, query, get_row, get_row.
The issue you seem to have run into is that the number in the ->at() matcher doesn't count up per method but per object.
So what you probably want to write is:
$query->expects($this->any())->method('query');
$query->expects($this->at(1))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(2))->method('get_row')->will($this->returnValue(False);
$query->expects($this->at(4))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(5))->method('get_row')->will($this->returnValue(False);
Or to make it a litte easer to read maybe even:
$query->expects($this->at(0))->method('query');
$query->expects($this->at(1))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(2))->method('get_row')->will($this->returnValue(False);
$query->expects($this->at(3))->method('query');
$query->expects($this->at(4))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(5))->method('get_row')->will($this->returnValue(False);
With your mocks you ran into the issue that the second call to "query" was counting up one "call" and hence skipping over the second return($test_data);.
Unfortunately the at() binds your tests to the implementations very strongly.
Imagine if you rearranged 2 method calls inside a tested method, the functionality is exactly the same but all tests using at() would now fail, often with cryptic messages such as method doesn't exist at index N
On the occasions you want to specifically say "this called exactly this way then this called exactly this way" that's great, but if you just want assertions then one of the PHPUnit Mock Extensions seems more friendly, particularly Mockery and a guide here (a touch out of date I believe)
There are others also.
Related
I’m just diving into testing/test driven development in php, and am a bit confused about how to handle a very typical scenario in our legacy codebase. Our codebase does not have many classes, it’s very functional programming oriented.
In this example code, which is pseudocode, I want to write a test for getAndOrderUsersByAge(). As you can see it first gets all the users from our database (in a giant array lets say, because we don’t have objects/classes) then sorts them. This is of course a VERY common occurrence in our code, manipulating or analyzing data gotten from the database; not objects, but an array of database rows basically.
Note that I've added an error in the if statement to simulate an error I want my tests to catch.
//gets all users in our db into an array, then sorts them by age
function getAndOrderUsersByAge(){
//get all users from the db
$all_users = getAllUsers();
//sort
$all_users_sorted = sortUsers($all_users)
//notice I've made a mistake here on purpose, left the "d" off of "$all_users_sorted"
//so the function will always return false, a mistake...which I want tests to catch
if(!empty($all_users_sorte){
return $all_users_sorted;
}
else{
return false;
}
}
My questions are:
1.) Is testing the function getAndOrderUsersByAge() unit testing, integration testing, or …?
2.) In my (unit?) test(s) of getAndOrderUsersByAge(), I want to assume that getAllUsers (the function which gets things from the db) behaves correctly because I only want to test getAndOrderUsersByAge() right? I’m not really sure what “concept” this falls under when I’m reading the two books I have on testing. Is this where I would “mock”, e.g. create some fake data for getAllUsers() to return?
3.) What tests would you write for getAndOrderUsersByAge()? I think I would write one that ensures a non-empty array is returned when getAllUsers() returns a non-empty array, as this would catch the error I've created. I'm not sure what else though.
Any tips, recommended reading material, etc. is very welcomed.
1.) Is testing the function getAndOrderUsersByAge() unit testing, integration testing, or …?
Any test that you do at code level is unit testing, basically testing methods , classes, interfaces. Tests that validate a unit or block of code.
2.) In my (unit?) test(s) of getAndOrderUsersByAge(), I want to assume that getAllUsers (the function which gets things from the db)
You can test getAndOrderUsersByAge() by actual data or mock data. Mock data is preferred so you will have more control on the test and you can create more test cases for sortUsers function.
3.) What tests would you write for getAndOrderUsersByAge()?
The test should be of nature that would validate your sortUsers function. I am not sure how your data looks but some tests could be:
1) data set that will have valid data , which will pass the test
2) data set that has invalid data like special characters in name
3) data set that has junk in age field or floating numbers or out of range integers, basically test how your function behaves when age has different type of data
4) data set that has null values
5) data set with 1 element array
6) data set with repeated element array
7) data set with invalid types in array
quick example of dataProvider:
return [
['180d-1pc', '6m-1pc'],
]
and test:
public function test_convert($title, $expected)
{
$uut = new Converter();
$this->assertEquals($expected, $uut->convertDayTitle($title));
}
(simply put: test if we convert 180d(days) to 6m(months))
as you can see - in data provider there are input data defined and also an expected output.
This works fine in many cases, but I keep having this feeling that maybe it's not the best idea. So I'm wondering if could be considered bad practice. If so - when I will see it was a bad idea to do it?
One example is, when you would like to use the same dataProvider in two tests - should you define two expected values and use one of them?
Quick example (I just made that up, so don't pay attention that I make product object just from title ;)):
public function test_gets_discount_when_licence_period_longer_than_1year($title, $expectedTitle, $expectedDiscount) {
$prod = new Product($title);
$this->assertEquals($expectedDiscount, $product->hasDiscount();
}
How to make this more elegant?
What you're doing is totally fine. Data providers can, and should, include the expected test result.
About the issue of reusing the same dataProvider for 2 tests, and only using some fields: I'd say that if the fields are related (i.e. they are properties of the same object), it can be acceptable.
If you feel the dataProvider is getting too big and complex because 2 tests must use data from it, just create 2 separate dataProviders, and use another private common method for building the common data.
Let me explain myself.
function insertObjects($objs) {
foreach ($objs as $obj) {
$this->repository->insert($obj);
}
}
I don't want to test that insertion into the database worked because I assume it works (it's a different unit). I also don't want to test foreach because obviously foreach is going to work. So the only thing to test here is that $objs is a well formed array. But if $objs is the mock data that I will be passing in... so does this mean there is nothing to test for this function?
If there's any chance of invalid input (a not well-formed array, null value, etc.), you need to handle that case in your method by explicitly checking for it.
In your test, you would then try to call your method with various invalid values, and check whether your method responds correctly, i.e. the database insert method is not called, exceptions are thrown, errors are logged, etc.
Other than that, the only thing to test is whether your database insert method is called with the parameters that correspond to the values in the valid test array you pass in.
I am using fatfree framework with the cortex ORM plugin. I am trying to get the number of records matching a specific criteria. My code:
$group_qry = new \models\system\UserGroup;
$group_qry->load(array('type=?',$name));
echo $group_qry->count(); //always returns 3, i.e total number of records in table
Initially I was thinking that this maybe because the filtering wasn't working and it always fetched everything, but that is not the case, cause I verified it with
while(!$group_qry->dry()){
echo '<br/>'.$group_qry->type;
$group_qry->next();
}
So how do I get the number of records actually loaded after filtering?
Yeah this part is confusing: the count() method actually executes a SELECT COUNT(*) statement. It takes the same arguments as the load() method, so in your case :
$group_qry->count(array('type=?',$name));
It is not exactly what you need, since it will execute a second SELECT, which will reduce performance.
What you need is to count the number of rows in the result array. Since this array is a protected variable, you'll need to create a dedicated function for that in the UserGroup class:
class UserGroup extends \DB\SQL\Mapper {
function countResults() {
return count($this->query);
}
}
If you feel that it's a bit of overkill for such a simple need, you can file a request to ask for the framework to handle it. Sounds like a reasonable demand.
UPDATE:
It's now part of the framework. So calling $group_qry->loaded() will return the number of loaded records.
xfra35 is right, there was currently no method for just returning the loaded items, when using the mappers as active record. alternatively you can just count the collection:
$result = $group_qry->find(array('type=?',$name));
echo count($result); // gives you the number of all found results
in addition, i've just added the countResults method, like suggested. thx xfra35.
https://github.com/ikkez/F3-Sugar/commit/92fa18130892ab7d2303edc31d2b1f4bba70e881
This is probably very easy to do, but I can't seem to get my head around it right now. Let's say in a component in a cakephp application, I have a variable my_model, which contains the model of the corresponding controller that is currently using the component like:
function TestComponent extend Object
{
var $my_model; // can be either User, or Person
function test()
{
$myModelTemp = $this->my_model;
$model = $myModelTemp != 'User' ? $myModelTemp.'->User' : 'User';
$this->$model->find('all');
}
}
As you can see above in my function test() what I'm trying to do is call the correct model based on the value of my_model. So based on the condition, my query will be either:
$this->Person->User->find('all');
Or
$this->User->find('all');
When I do it like I did above, I get an error saying Fatal error: Call to a member function find() on a non-object. In order words, that error means Person->User is not an object (so, it is considered as a string).
What you're saying could be true, however, it can refer to any part of the call.
So either Person or User could be invalid, or together they causes the error. Hard to say.
Try dumping the individual objects using var_dump();
So try:
<?php
echo "<pre>";
var_dump(is_object($this->Person));
var_dump(is_object($this->User));
echo "</pre>";
?>
to determine where you're code goes wrong.
To be clear, that return value needs to be true for it to be an object.
The one that returns false is the likely culprit.
Should your question refer to the correct way to reference an object, an object is basically an array. For example:
<?php
$obj = (object) array("this", "my_function");
?>
The above example casts the array as an object. However, using multiple layers might prove to be more difficult than you'd expect.
Generally, it looks like you might be going about this all wrong. Obviously you want the models to be dynamic, but then you're hard-coding things which defeats the whole point of it being dynamic in the first place.
It also seems like you might be violating the principals of CakePHP and MVC by doing all this in a component. I'm not sure this component should really be manipulating models or assuming which models are currently in use.
However, if you want to evaluate a string as an actual object, you can wrap it in { ... } (this is valid standard PHP syntax, not Cake-specific code).
Try this:
$modelName = $this->my_model;
$model = ($modelName != 'User') ? $this->{$modelName}->User : $this->User;
$model->find('all');
Now, if this doesn't work or you get an error saying it can't find the model(s) you need to ensure the models are actually loaded and initialised in the current scope.