2

how would you test a method of a class that has collateral effects via self::? such as:

final class Foo{
    private static $effect = false;
    public static function doit( $arg ){
        if( self::effect ) return;
        self::check_args( $arg );
        self::$effect = true;
    }
    private function check_args($arg){
        #validate and trhow exception if invalid
    }
}

I have to send several args to doit() to test it's validating the values correctly, but after the first run it's just bypassing it. it's basically a initialization method for a singleton that set's a initialized flag.

I can't really afford to mess with the class just yet. Is there any way i can make a copy/instatiate of the object in a way it would work with self::?

hakre
  • 193,403
  • 52
  • 435
  • 836
gcb
  • 13,901
  • 7
  • 67
  • 92
  • That's not valid syntax. – Jon Mar 20 '12 at 22:27
  • 1
    Did you try the --static-backup option in PHPUnit? – SamHennessy Mar 20 '12 at 22:41
  • @SamHennessy that seems to go in the right direction, but i failed to find more info on it. from http://www.phpunit.de/manual/3.6/en/textui.html it redirects me to http://www.phpunit.de/manual/3.6/en/fixtures.html#fixtures.global-state which does not elaborate on that flag at all. i tried running the test with the flag, but it's just like before. – gcb Mar 20 '12 at 22:50
  • @Jon i've updated the code to be correct, my bad. – gcb Mar 20 '12 at 22:51
  • @tereško i'd love to get to the refactoring part of this project. – gcb Mar 20 '12 at 22:51
  • I see you making internal static references, but the variable itself is not declared as static - so I can't see how this would do what you want. Same with doit() - you wouldn't be able to call this statically. Hard to call it a singleton without having a static declaration. See singleton section here: http://php.net/manual/en/language.oop5.patterns.php – Mike Hedman Mar 21 '12 at 04:07
  • @MikeHedman sorry, my bad. i don't know why i omited the 'static' and 'final's when typing a simplified version of the class – gcb Mar 21 '12 at 18:29

2 Answers2

2

The class should be rewritten to:

class Foo{
    private $effect = false;
    public function doit( $arg ){
        if( $this->effect ) return;
        $this->check_args( $arg );
        $this->effect = true;
    }
    private function check_args($arg){
        #validate and trhow exception if invalid
    }
}

self:: is only used for static functions and variables, which you should avoid using in most cases for various reasons.

dqhendricks
  • 19,030
  • 11
  • 50
  • 83
  • my bad, i forgot the 'static' keywords when typing that simpler version of the class. the question i think is more how to reinitialize the object declaration somehow to test it with several initialization parameters on the same phpunit run. – gcb Mar 21 '12 at 18:30
  • @gbc People would generally tell you that using statics in most cases is bad practice. It's basically the same as using a global variable. It means that the same function may not have the same result depending on the state of the global variable. Someone could inject something into any part of your code that causes a bug, and you may never be able to find it. Does this make sense? There are a few cases where it can be used without worry, like object registry. Statics are also notoriously hard to unit test. I would suggest to learn about dependency injection instead of using global statics. – dqhendricks Mar 21 '12 at 19:10
  • I agree with all that. but i was dragged to this project. I'm trying to write tests to the project to then push for a refactor. – gcb Mar 21 '12 at 21:09
  • @gbc how big of an application is it? with code like this, is a rewrite an option? might be an equal amount of work. PHPUnit can back up static variables, then restore them to their original state. – dqhendricks Mar 21 '12 at 22:53
  • it's not huge. pretty doable in a couple sprints. Mostly politics, but do you have more info about that? other people mentioned it as well in the comments, but i could only find about global vars, nothing about static methods. also, this class has private methods and no getter setters. – gcb Mar 21 '12 at 23:51
  • 1
    @gcb you'll find it under annotations in the manual http://www.phpunit.de/manual/3.6/en/appendixes.annotations.html – dqhendricks Mar 22 '12 at 01:24
1

How about adding to your testing environment a subclass of your class under test, and then using it to manipulate your static variable. In the example below, I modified your code a bit so the value of $effect is easier to play with, and I also had to bump the scope of $effect to protected:

<?php
class Foo{
    protected static $effect = "0";
    public static function doit( $arg ) {
        echo 'I got: ' . $arg . ' and effect is: ' . self::$effect . '<br>';
        self::$effect = "1";
    }
}

class FooChild extends Foo {
    public static function setEffect($newEffect) {
        self::$effect = $newEffect;
    }
}

Foo::doit('hello');
Foo::doit('world');
FooChild::setEffect('3');
Foo::doit('three');

The output is this:

I got: hello and effect is: 0 (first time through shows the initial value)

I got: world and effect is: 1 (second time through shows the value incremented by Foo in doit()

I got: three and effect is: 3 (third time through, shows that the subclass was able to change the value in the parent)

Mike Hedman
  • 1,391
  • 1
  • 11
  • 30
  • Excellent idea! that may not be the best pure unittest (not only you are not testing only one method, but you are adding new methods)... but may solve the more immediate problem. and after all, the main problem i agree is in the original code to begin with. thanks, i'm implementing this until someone points to a better pattern (if it's possible in this case) – gcb Mar 21 '12 at 21:13
  • Well, the difference is that the child class would live in your test world - not in the production code. So I consider that it's part of the test fixture. Not dissimilar to code that you would put in a test setup or teardown. A similar tactic is to use a seam, see: http://stackoverflow.com/questions/7210851/phpunit-how-to-mock-todays-date-without-passing-it-as-an-argument/7410887#7410887 But with statics things are a bit trickier. – Mike Hedman Mar 21 '12 at 23:43