mercredi 28 avril 2021

Is there a way to unit test a function with static variables?

Ιs there a way to test a function containing static variables using PHPUnit?

For example,

class MyClass {
  public function my_method() {
    static $already_invoked = false;
    
    if ($already_invoked) {
      return;
    }

    echo 'Hello world';

    $already_invoked = true;
  }
}

my_method() will print Hello world exactly one time, no matter how many times it's called.

$instance = new MyClass();
$instance->my_method();
$instance->my_method();

outputs:

Hello world

Having just one test

<?php
class MyMethodTest extends \PHPUnit\Framework\TestCase {
    public function test_should_print_hello_world_exactly_one_time() {
        $instance = new MyClass();
        $instance->my_method();
        $instance->my_method();

        $this->expectOutputString('Hello world');
    }
}

passes:

MyMethod
 ✔ Should print hello world exactly one time

However, adding another test

<?php
class MyMethodTest extends \PHPUnit\Framework\TestCase {
    public function test_should_print_hello_world() {
        (new MyClass())->my_method();

        $this->expectOutputString('Hello world');
    }

    public function test_should_print_hello_world_exactly_one_time() {
        $instance = new MyClass();
        $instance->my_method();
        $instance->my_method();

        $this->expectOutputString('Hello world');
    }
}

causes the previously passing test to fail, because of the static variable:

MyMethod
 ✔ Should print hello world
 ✘ Should print hello world exactly one time
   │
   │ Failed asserting that two strings are equal.
   │ --- Expected
   │ +++ Actual
   │ @@ @@
   │ -'Hello world'
   │ +''
   │

Now, I know I could use a static property instead:

class MyClass {
    private static bool $already_invoked = false;

    public function my_method() {
        if (self::$already_invoked) {
            return;
        }

        echo 'Hello world';

        self::$already_invoked = true;
    }
}

That way, I could use a reflection property to reset its value immediately after each test, like this:

class MyMethodTest extends \PHPUnit\Framework\TestCase {
    // ...

    public function tearDown() {
        $ref = new ReflectionProperty('MyClass', 'already_invoked');
        $ref->setAccessible(true);
        $ref->setValue(null, false);
    }
}

And then, both tests pass:

MyMethod
 ✔ Should print hello world
 ✔ Should print hello world exactly one time

In my case, though, I'm dealing with legacy code and I'd like to write some unit tests before refactoring it.

I've also tried to run each test in isolation (although that's not ideal since it'd probably end up being considerably slower) using the @runInSeparateProcess method annotation, but that seems to be failing because the process isolation feature requires serialization:

PHP Fatal error:  Uncaught Exception: Serialization of 'ReflectionClass' is not allowed in phar:///usr/local/bin/phpunit/phpunit/Util/GlobalState.php:151

Is there a way to test a function containing static variables?





Aucun commentaire:

Enregistrer un commentaire