12

Is there a way to create a mock class, as opposed to a mock object, with phpunit? I'm looking for a way to do dependency injection without having to explicitly pass every object a class might need to work with in the constructor (or wherever). Something that will return "true" for all of these cases:

public function testAAAA()
{
  $foo = $this->getMock('foo', array('bar'));
  var_dump(class_exists('foo', false));
  var_dump(method_exists('foo', 'bar'));
  var_dump(method_exists($foo, 'bar'));
}

This prints:

bool(true)
bool(false)
bool(true)

indicating that while it did successfully create a fake 'foo' class it did not bind a 'bar' method to it.

I am using phpunit 3.7.5.

1 Answers1

14

I suspect that you don't actually want to do this (as you can disable constructors and so on with PHPUnit's mockbuilder, see the docs ), but assuming you do want or need to, this should do the trick:

$foo = $this->getMockBuilder('nonexistant')
        ->setMockClassName('foo')
        ->setMethods(array('bar'))
        ->getMock();

    var_dump(class_exists('foo', false));
    var_dump(method_exists('foo', 'bar'));
    var_dump(method_exists($foo, 'bar'));

    $cls = new ReflectionClass('foo');
    var_dump($cls->hasMethod('bar'));

I'm honestly not sure about the specifics of why you need to specify different names (nonexistant and foo) above, but it seems to have to do with PHPUnit's behavior when the class being mocked doesn't exist yet, and having setMockClassName generate a class extending that class. Or Something. It's probably effectively working around a bug/edge-case -- this is odd usage of the library. You should be able to do the same thing through the getMock function alone, it's just uglier.

As an aside, it sounds like you should probably get familiar with php's reflection capabilities. It's not the most powerful reflection library out there, but it's pretty nice. I've used it to generate meta-information about required and optional fields for a class based on their constructor arguments and properties for a "model" library, where that meta-information is used to generate forms that accept the correct types of values. That is, generate typed forms without instances of the class the form is for, and without hand-writing a stupid amount of code -- it's about 100 lines in all for the entire feature. Obviously I don't know what you're trying to do, but from the small amount of info in your post, I'd guess it's closer to that type of meta-thing than not.

jdd
  • 4,301
  • 1
  • 27
  • 28
  • 1
    I wouldn't say that I necessarily want to do this, but it is what I must do. I need to be able to generate a mock object that will respond to calls made inside the test, without changing tens of thousands of files to add true DI. This getMockBuilder method gets me close, but it looks like phpunit generates bad eval code when it is called with a namespaced mock class name (something I'm using that I should have mentioned in the original post). –  Nov 15 '12 at 01:15
  • 1
    add a call to `class_alias` like `class_alias('foo', 'quux\baz')` after the `getMock()` call, and then test against `quux\baz`. That said, I think you're basically at the point where you might want to consider patching PHPUnit, or using ReflectionClass and ReflectionObject's ability to add methods to an object or class on the fly, and you're going to be hitting edge cases. This is not an area where PHP shines. Also, if you haven't read it, you *need* a copy of "Working Effectively With Legacy Code" by Michael Feathers. – jdd Nov 15 '12 at 01:57
  • Thanks. I'll try class_alias. I'm also looking at patching PHPUnit, modifying the template it uses in an eval(). As to the book, what I really need is to get the bosses to read up. :) –  Nov 15 '12 at 02:31