I am refactoring a class while writing unit tests for it.
There is a case that one of my methods is completely calling another object's methods which is injected to this class that I am testing.
So I have to mock the object that I have injected to class.
Now, the questing is that, does it worth to write unit tests for this particular method?
It seems to be strange to write unit tests which all it does it calling other object's methods which that object itself has to be mocked, then why am I testing this method at all?
Isn't the purpose of testing to check functionality of a method whether it works as expected or not?
If it is, then when I am mocking all it has and there is nothing left of that particular method to test then why should I test?
I am really confused!
The method which I'm stuck at is this (which is used for custom session handling):
public function write($sessionId, $sessionData)
{
$sth = $this->databaseConnection->prepare("INSERT INTO `{$this->DBTableName}` (`session_id`,`session_name`,`session_data`) VALUES(:session_id, :session_name, :session_data) ON DUPLICATE KEY UPDATE `session_data`=:session_data;");
$sth->bindValue(':session_id', $sessionId);
$sth->bindValue(':session_name', $this->sessionName);
$sth->bindValue(':session_data', $sessionData);
return $sth->execute();
}
here is link for this piece of code as well: http://pastebin.com/1FBeU6mb
By the way, I am newly started writing tests for my classes and I am beginner in this field of testing and inexperienced.
Thanks in advance.
I'm not really familiar with php, but it looks like you're just building and executing a database query, right?
Unless there's some other layer between this method and the database later that I don't see, there's really nothing here worth mock away. So you're right in the sense that mocking here does not give you a lot of value. And in one sense, testing this method is of limited value since you are really just testing the database layer, which generally we can assume is already correct and stable, and therefore does not need tests.
The value of mocking in general is that it allows you to simplify your tests by making assumptions about what other methods are doing, and allows you to not have to test those other methods indirectly.
In choosing to test the write method, what it turns out you are testing is that you have the correct steps in place to return the right results. That's it. I'm not familiar with php mocking frameworks, but I do know for frameworks in other languages you can set up what are called expectations, which lets you specify that a certain method will be called on a certain object with certain parameters. You can even often specify the order in which they should be executed. The takeaway here is that you are testing the outgoing messages your object is sending, and not the return values of those messages.
It is up to you to decide whether that is valuable vs. the maintenance this test will require.
You are testing if the right arguments are passed to your prepared statement. Besides you should test if the write method returns the prepared statment result.
If you do not test this there are several ways your application could break.
Renaming or removing of the methods arguments ($sessionId, $sessionData)
Renaming of your property $this->sessionName
Removing one of the bindValue calls.
Wrong naming of your bind aliases. They should match your query.
Returning something else than the result of execute().
etc. etc.
So yes it is good pratice to test this.
Assuming that your example describes a SessionHandler class, which looks similar to this:
class SessionHandler
{
public function __construct($sessionTableName, $sessionName, \PDO $databaseConnection)
{
$this->DBTableName = $sessionTableName;
$this->sessionName = $sessionName;
$this->databaseConnection = $databaseConnection;
}
// among others, your method write($sessionId, $sessionData) follows
}
this could cover the method write():
public function testWriteInsertsOrUpdatesSessionData()
{
/**
* initialize a few explaining variables which we can refer to
* later when arranging test doubles and eventually act
*/
$sessionTableName = 'sessions';
$sessionName = 'foobarbaz';
$sessionId = 'foo';
$sessionData = serialize([
'bar' => 'baz',
]);
$executed = true;
/**
* create a test double for the statement that we expect to be returned
* from `PDO::prepare()`
*/
$statement = $this->createMock(\PDOStatement::class);
/**
* set up expectations towards which methods should be invoked
* on the statement, specifying their order
*/
$statement
->expects($this->at(0))
->method('bindValue')
->with(
$this->identicalTo(':session_id'),
$this->identicalTo(sessionId)
);
$statement
->expects($this->at(1))
->method('bindValue')
->with(
$this->identicalTo(':session_name'),
$this->identicalTo($sessionName)
);
$statement
->expects($this->at(2))
->method('bindValue')
->with(
$this->identicalTo(':session_data'),
$this->identicalTo(sessionData)
);
$statement
->expects($this->at(3))
->method('execute')
->willReturn($executed);
/**
* create a test double for the database connection we inject
* into SessionHandler during construction
*/
$databaseConnection = $this->createMock(\PDO::class);
$databaseConnection
->expects($this->once())
->method('prepare')
->with($this->identicalTo(sprintf(
'INSERT INTO `%s` (`session_id`,`session_name`,`session_data`) VALUES(:session_id, :session_name, :session_data) ON DUPLICATE KEY UPDATE `session_data`=:session_data;',
$sessionTableName
)))
->willReturn($statement);
$sessionHandler = new SessionHandler(
$sessionTableName,
$sessionName,
$databaseConnection
);
$result = $sessionHandler->write(
$sessionId,
$sessionData
);
$this->assertSame($executed, $result);
}
For reference, see:
https://phpunit.de/manual/current/en/test-doubles.html
Related
I'm doing unit testing and my repository has this method:
/**
* #param int|Collection $id
* #return MyModel|Collection|null
*/
public function find($id);
I tried this:
$this->package_repo = Mockery::mock(PackageRepositoryInterface::class);
$this->app->instance('Package', $this->package_repo);
// ...
$this->ticket_repo->shouldReceive("find")->with(collect([]))->andReturn(/*...*/);
//...
echo $this->ticket_repo->find(collect([])); // Failed here.
The test immediately failed, and I think it's because the 2 collections in the shouldReceive expectation statement and the actual find statement are distinct objects.
If I want to mock the behavior of $this->ticket_repo->find($ids), what should I do?
Or rather, if there is a better way to design a testable algorithm, how would it be? Because I would like to have all the ids to I want to find be in an array, and be constructed into a single SQL select query, instead of a few hundred separate ones, for performance purposes.
Also asked here: https://laracasts.com/discuss/channels/testing/mocking-findcollection-in-repository-pattern-in-laravel-with-mockery#
You have to mock a concrete class and not the interface. Since you haven't provided the name I'm just guessing:
$mock = Mockery::mock(TicketRepo::class);
$this->app->swap('PackageRepo', $mock);
So now whenever someone asks for an instance by the Interface name they'll get the mock.
I have a method that uses another class to calculate its outcome, that I want to test using PHPUnit.
/**
* Returns true if the given user has been granted the given permission.
*
* #param User $user
* #param AbstractPermission $permission
* #return bool
*/
public function userPermissionGranted(User $user, AbstractPermission $permission) : bool
{
// Retrieve model from database.
$user_permission = UserPermission::scopeUser($user)
->scopePermission($permission)
->first();
return $user_permission ? $user_permission->isGranted() : $permission->isGrantedByDefault();
}
Leaving out of consideration what this method actually does, I am wondering how to test this method. I can pass mocks of the User and AbstractPermission classes to the method, but the UserPermission class that is used inside the method's body (to retrieve a model from the database) I can do nothing with.
On top of that, if I pass mocks of the User and Permisson classes, they won't exist in the database, so when UserPermission queries the database, it will receive no results and the method will fail.
What do I do here? Is it considered good practice to simply mock the database (i.e. copying the live db structure and filling it with test data) and let my model query that database, and just trusting that everything is OK? Any suggestions on what to do here?
On a side note, UserPermission is an Eloquent model. I am merely making use of Eloquent here - without Laravel.
As a general rule, you can't directly mock static methods - at least, there's no good way to do it. Depending on how your application is set up, you might be able to hack something together that involves redefining the method with runkit or perhaps messing with includes/autoloader to load a mock class instead of the real one, but such solutions are kludgey at best.
One simple approach to allow unit-testing would be to wrap your static method calls in an instance method. So you'd create a new class with instance methods that call the static methods. Of course, you wouldn't be able to test that new class, but if it's a thin wrapper around the static methods then there's not really any value in testing it anyway.
So you might end up with something like this, for example:
class UserPermissionWrapper {
public function getUserPermission($user) {
return UserPermission::scopeUser($user);
}
}
Then you can inject that into your original class and get something like this:
public function userPermissionGranted(User $user, AbstractPermission $permission) : bool
{
// Assume this is an instance of UserPermissionWrapper injected at construction
$user_permission = $this->userPermissionWrapper
->getUserPermission($user)
->scopePermission($permission)
->first();
return $user_permission ? $user_permission->isGranted() : $permission->isGrantedByDefault();
}
Now you have an object calling instance methods, so you can inject a mock version of that class and set up the method calls in the normal way.
To answer my own question - and I've only come to a reasonable answer after a while of writing some more unit tests - I guess what it comes down to is this:
When testing the userPermissionGranted() method, we're actually only validating that the method works as expected. We're fetching a model from the database, and we may assume that this model has been tested already in its own, separate test. Given that we may assume that this model works as intended, and that we cannot access the database here to fetch the actual model, we can use a mock of the model, which we customly build to work just the way it should work, without actually performing any database work. That's where Peter Geer's answer comes in. Our class should contain a method to fetch the model from the database, so that instead of fetching and returning the model from the database, we can set up a mock and return that instead. In this case that means that in the return line of the method we're testing, we're going to test a mocked isGranted() on $user_permission (which is the mock we created to return a value that we want it to return), and isGrantedByDefault() on the $permission mock that we passed to the method when we called it.
I have this method that I would like to unit test. Since it's creating the Sql object inside the method, I can't mock it.
Initially I thought about making Sql be an instance property except that I'd have to reset it every time I use it in other methods and this will most likely lead to hard to debug errors (I don't want the possibility to get a "dirty" Sql object on other subsequent calls to its getter if at all avoidable).
What's the common pattern for testing these kinds of methods?
public function getConfigFromDb()
{
if (!is_null($this->configInDb)) {
return $this->configInDb;
}
$sql = new Sql($this->getSlaveDbAdapter());
$select = $sql->select()
->from('mytable');
$statement = $sql->prepareStatementForSqlObject($select);
$results = $statement->execute();
$results->buffer();
$this->configInDb = $return;
return $results;
}
#Julian you're right, I'll put it as an answer
In this case, I would add a SqlFactory and register it as a service (in factories param in service manager config), that way you can easily mock the service and the objects you'll get from it.
To go further on the subject, I delegate all object creation to factories that I can call using ServiceManager.
The fact is, that way, I can test my Factory in isolation, injecting all dependencies it needs, asserting that the actual object created is the expected object. And anytime I need an object from Factory in the method tested, I can serve a real instance, or a Mock.
I've been trying to get the PHPUnit mock objects working for some legacy code I'm working on, but I'm having issues getting it to sub in for the object I'm after and I'm pretty sure it must be because I'm using it wrong.
Currently we have a class which is used purely for creating queries, when another class wants to make a query it creates an object of this class and passes it the sql and database details. What I want to do is have PHPUnit replace this object with a mock version which I can test.
What I've been finding though is, if I create the mock object in the test script, the method being tested just bypasses it. I'm guessing this is because the method is creating and then using the object locally rather than it being passed as a parameter (in which case I could just reference the method with the mock object passed as a parameter). Below is an example of what the code might look like:
class SampleClass{
function loadData(){
$sql = "SELECT * FROM users";
$query = new Query();
$query->query($sql);
while($row = $query->get_row()){
if($row['email'] = ''){
$this->errors[] = "Email missing from user ".$row['user_id'];
}
$result[] = $query->get_row();
}
$this->users = $result;
if(count($user->errors) >= 1){
return $user->errors;
}else{
return true;
}
}
}
class Query{
function query($sql){
$this->result = mysql_query($sql);
}
function get_row(){
return mysql_fetch_assoc($this->result);
}
}
Is there a way to create a mock object in the PHPUnit test file which will replace the $query object in SampleClass with a mock object which I can use to test the parameters being passed to and control the response of? I won't be able to replace the query class or change how it's referenced, as it's used extensively throughout our application, but I would like to at least be able to create some form of test framework for it. Would appreciate any help you can give
Edited to clarify that there's more than just the query happening in the loadData method, which is the part of the method I'm trying to test. I'm hoping to sub in a double of the query class method get_row which will return a pre-set array of sample data for the method to work with rather than it hitting an actual database
Yes that is possible.
However, you can just create a stub of the SampleClass your own. And you can make the classname or instance of Query a dependency, too. So you can actually inject it. I'd say that's a better way and much easier for you to write tests then.
I'm having a problem with a line of code like:
$user->has('roles', ORM::factory('role', array('name' => 'unverified')))
I can mock the first argument, but can only assert that the 2nd argument returns a class. In some classes I make multiple uses of has and need to be able to test them properly. To be clear, I need to confirm that "unverified" was passed into the factory method of the 2nd argument. Any help is much appreciated.
The class I'm testing:
<?php
/**
* Policy class to determine if a user can upload a file.
*/
class Policy_File_Upload extends Policy
{
const NOT_LOGGED_IN = 1;
const NOT_VERIFIED = 2;
public function execute(Model_ACL_User $user, array $extra = NULL)
{
if ($user->can('login'))
{
return self::NOT_LOGGED_IN;
}
elseif ($user->has('roles', ORM::factory('role', array('name' => 'unverified'))))
{
return self::NOT_VERIFIED;
}
return TRUE;
}
}
and the corresponding test:
public function test_guest()
{
// User mock
$user = $this->getMock('Model_User', array('can', 'has'), array(), '', FALSE);
$user->expects($this->any())->method('can')->with('login')->will($this->returnValue(TRUE));
$user->expects($this->any())->method('has')->with('roles', $this->logicalOr
(
))
->will($this->returnCallback(array($this, 'roles')));
$policy = new Policy_File_Upload;
$this->assertSame(Policy_File_Upload::NOT_VERIFIED, $policy->execute($user));
}
You can always go the data-driven route of preparing test data for each case and verify the result of calling the function. In the end, you don't so much care that 'unverified' gets passed to ORM::factory() but rather that the correct result comes out of execute() based on the input.
Testing the implementation details makes the test brittle and difficult to write. If you can test the results instead, your tests won't break when you change how you arrive at the result. It may take longer to put together a data set for all your use cases, but it can generally be shared among tests.
To be clear, I need to confirm that "unverified" was passed into the factory method of the 2nd argument.
<?php
public function execute(Model_ACL_User $user, array $extra = NULL)
[...] ORM::factory('role', array('name' => 'unverified')) [...]
I'm sorry mate, you can't do that as there is no (sane) way to mock out a static method call.
The argument to why this is a problem are similar to one of Sebastian Bergmanns blog post: Testing-Code-That-Uses-Singletons
There are options like runkit that you could choose to replace the "ORM" class at runtime with a mocked version but thats really painfull.
So there are a couple of options:
You could create something like a Policy_File_Upload_Data_Provider that has a ->getRoles method where you put your ORM access. That would allow you to test your business logic nicely and testing that data access class should be a little easier as it doesn't to that much (just accessing the orm).
You could Inject your ORM class (or if that doesn't work out maybe just its name).
Depending on the class its self you should be able to create an instance and call ->factory like it would be a normal method. You could mock that.
If you don't want that just leaving that in there is also a not SO bad option.
Hope that at least gave you an idea/overview.
Additional Links why it's hard to test statics:
Misko Hevery - Flaw: Brittle Global State & Singletons
Kore Nordmann - Static considered harmful
You can actually do verification on parameters passed to mock methods utilizing parameter capturing in the Phake mocking framework for PHP.
public function test_guest()
{
// User mock
$user = Phake::mock('Model_User');
when($user)->can('login')->thenReturn(FALSE);
when($user)->has('roles', $this->anything())->thenGetReturnByLambda(array($this, 'roles'));
$policy = new Policy_File_Upload;
$this->assertSame(Policy_File_Upload::NOT_VERIFIED, $policy->execute($user));
Phake::verify($user)->has('roles', Phake::capture($factoriedRole));
$this->assertEquals('unverified', $factoriedRole->get('name'));
}
This example is assuming that you have the ability to check the name on $factoriedRole, obviously that piece needs to be changed depending on how that ORM stack works.
A small note, I agree with edorian regarding statics. If possible I would always prefer redesigning your code to not require statics, but sometimes that isn't possible, in which case this is a workable solution.
Phake Github Page
Phake Documentation on Parameter Capturing