Mock Objects in PHPUnit to emulate calls to static methods?

I am trying to test a class that controls access to data in a database (you know, CRUD, essentially). The DB library we use has an API in which you first get the table object with a static call:

function getFoo($id) { $MyTableRepresentation = DB_DataObject::factory("mytable"); $MyTableRepresentation->get($id); ... do some stuff return $somedata } 

... you get the idea.

We are trying to test this method, but we are mocking DataObject so that (a) we donโ€™t need the actual db connection for the test, and (b) we donโ€™t even need to include DB_DataObject lib for the test.

However, in PHPUnit, I cannot get $ this-> getMock () to set up a static call. I have...

  $DB_DataObject = $this->getMock('DB_DataObject', array('factory')); 

... but the test still talks about the unknown "factory" method. I know that he creates an object because he said earlier that he could not find DB_DataObject. Now it is possible. But, no method?

What I really want to do is have two mock objects, one for the returned table object. Thus, I not only need to indicate that the factory is a static call, but also it returns some specific other layout that I have already configured.

I should mention as a warning that I did this in SimpleTest some time ago (I cannot find the code), and it worked fine.

What gives?

[UPDATE]

I'm starting to realize that it has something to do with expectations ()

+4
source share
6 answers

I agree with both of you that it is better not to use a static call. However, I think I forgot to mention that DB_DataObject is a third-party library, and a static call is the best practice for using the code, not ours. There are other ways to use your objects that are related to the direct construction of the returned object. It simply leaves these damned include / require statements in any class file using this DB_DO class. This sucks because the tests will break (or simply not be isolated) if you meanwhile try to make fun of a class with the same name in your test - at least I think.

+2
source

If you cannot change the library, change its access. Refactoring all DB_DataObject :: factory () calls to an instance method in your code:

 function getFoo($id) { $MyTableRepresentation = $this->getTable("mytable"); $MyTableRepresentation->get($id); ... do some stuff return $somedata } function getTable($table) { return DB_DataObject::factory($table); } 

Now you can use the partial layout of the class you are testing, and getTable () returns a mock table object.

 function testMyTable() { $dao = $this->getMock('MyTableDao', array('getMock')); $table = $this->getMock('DB_DataObject', ...); $dao->expects($this->any()) ->method('getTable') ->with('mytable') ->will($this->returnValue($table)); $table->expects... ...test... } 
+2
source

This is a good example of the dependency in your code - the design made it impossible to inject into the Mock, and not in the real class.

My first suggestion is to try and reorganize the code to use an instance rather than a static call.

+1
source

What is missing (or not?) From your DB_DataObject class is a means to pass the prepared db object before calling the factory method. This way you can pass the layout or user db object (with the same interface) if necessary.

In your test setup:

  public function setUp() { $mockDb = new MockDb(); DB_DataObject::setAdapter($mockDb); } 

The factory () method should return an instance of mocked DB. If it is not yet integrated into your class, you may have to reorganize the factory () method to make it work.

0
source

Do you need / including a class file for DB_DataObject in a test case? If the class does not exist before PHPUnit attempts to mock the object, you may receive such errors.

0
source

With the PHPUnit MockFunction extension plus runkit, you can also simulate static methods. Be careful because this is a monkey fix and therefore should only be used in extreme cases. Not a substitute for good programming techniques.

https://github.com/tcz/phpunit-mockfunction

0
source

All Articles