5

I have some very test-unfriendly code (to say the least) that I need to test. Refactoring unfortunately is not an option. I have to test the code as it is, without the possibility of changing it.

To do that, I was thinking of intercepting function calls and dynamically change what they do so I can run my tests, as I need some functions and methods to return known values, and I need others that make requests, connect to the database, etc, to stop doing that and return what I need them to return. Is there any way to do this without runkit_method_redefine(), which is preferably not "EXPERIMENTAL" and still maintained? Maybe an alternative to runkit? Maybe a better way?

Edit: will use PHPUnit's test doubles and PHP 5.3.2's features for making private methods accessible, if I need that functionality.

PeeHaa
  • 71,436
  • 58
  • 190
  • 262
rid
  • 61,078
  • 31
  • 152
  • 193
  • 2
    Anything that messes around in the guts of PHP like runkit does will always be "experimental". You can do open heart surgery on yourself, but it's not generally a good idea... – Marc B May 27 '11 at 15:29

2 Answers2

15

Note: the Test-Helper extension is superseded by https://github.com/krakjoe/uopz

PHPUnit's Test Helper extension (PECL) allows redefiniton/intercepting/stubbing/mocking of hardcoded dependencies with your own implementations:

protected function setUp()
{
    $this->getMock(
      'Bar',                    /* name of class to mock     */
      array('doSomethingElse'), /* list of methods to mock   */
      array(),                  /* constructor arguments     */
      'BarMock'                 /* name for mocked class     */
    );

    set_new_overload(array($this, 'newCallback'));
}

It also allows intercepting the exit statement and instance creation:

For stubbing and mocking methods you simply use PHPUnit's regular mocking framework. See

You can also use Mockery with PHPUnit:

Another option would be to use http://antecedent.github.io/patchwork

Patchwork is a PHP library that makes it possible to redefine user-defined functions and methods at runtime, loosely replicating the functionality runkit_function_redefine in pure PHP 5.3 code, which, among other things, enables you to replace static and private methods with test doubles.

Gordon
  • 312,688
  • 75
  • 539
  • 559
  • This looks like a great package, but I'm not exactly sure how I can redefine a method... Maybe I'm missing something. I need something similar to `runkit_method_redefine('MyClass', 'myMethod', '', 'return 5;');` – rid May 27 '11 at 15:41
  • @rdineiu It's explained in the example at the bottom of the Github page. You rename the functions, like `rename_function('foo', 'foo_orig'); rename_function('foo_stub', 'foo');` which is effectively the same as redefining. – Gordon May 27 '11 at 15:45
  • @Gordon, that works for functions, but how about methods? I need to overwrite the code in a method. – rid May 27 '11 at 15:48
  • @rdineiu for stubbing and mocking methods you simply use PHPUnit's regular mocking framework. See http://www.phpunit.de/manual/current/en/test-doubles.html – Gordon May 27 '11 at 15:49
  • @Gordon, test doubles look almost perfect, but what can I do if I need to replace private or static methods? – rid May 27 '11 at 15:59
  • @rdineiu PHPUnit can mock static methods as well: http://sebastian-bergmann.de/archives/883-Stubbing-and-Mocking-Static-Methods.html - You do not mock or stub private methods. You only stub/mock public methods because those are the ones your test subject will interact with. – Gordon May 27 '11 at 16:44
  • @Gordon, well, I have something like this: `public function someMethod() { lots of code, $this->privateMethod(), some more code }`... I need to change the behavior of that private method. It normally connects to some database or sends some web request, etc. But I need to make it return something I define, so that I can see if `someMethod()` does what it should do. – rid May 27 '11 at 16:50
  • Aha! Your link is a great tutorial. I see it can be done with PHP 5.3's `setAccessible()`. Thanks! – rid May 27 '11 at 16:59
  • @rdnineiu nope. think about it for a second. A mock/stub is a replacement for a dependency, e.g. a replacement for something that the code you want to test requires. That code will only interface with its dependencies through the dependencies public API. Consequently, you dont stub one method within the depedency, but stub the public methods only to return a defined value. You dont stub just the private method. Stub the whole thing you are calling. – Gordon May 27 '11 at 17:02
  • @Godron, I get what you mean... If that private method does stuff, it needs to call public methods from other classes in order to accomplish what it does, so I'd need to mock these instead. But it doesn't always call other classes' public methods, it does some stuff on its own. For example, the method might not use any public methods, and instead, simply call `mysql_query()`. What should I do in this situation? Rename the function `mysql_query()` and analyze what parameters it receives, so I can alter it to return something specifically for that private method to use? – rid May 27 '11 at 17:08
  • @rdnieiu You dont test private methods of the test subject at all. UnitTests test public API. Any non-public methods are tested implicitly by testing the public API. And you dont mock/stub anything but the public API of any depedencies either, but just mock/stub the public methods of those. If you are unsure about this, open a new question with some example code. That gives others the chance to add their answers too. – Gordon May 27 '11 at 20:53
2

The runkit extension is a perfect solution for your needs. It is proven by years of my personal experience and described in many presentations and articles authored by different authors in the internet.

I can assure you that the runkit_method_redefine function as well as the whole runkit extension is not experimental anymore (documentation hosted on the php.net is obsolete). The up-to-date runkit extension can be found on http://github.com/zenovich/runkit

Sincerely, Dmitry Zenovich