Handle static access in TestingAccessWrapper
authorGergő Tisza <gtisza@wikimedia.org>
Mon, 25 Jan 2016 04:16:40 +0000 (04:16 +0000)
committerGergő Tisza <tgr.huwiki@gmail.com>
Mon, 25 Jan 2016 04:24:10 +0000 (22:24 -0600)
Change-Id: Ia8ec6ee0a364807e78fc182c4ce99d782b921eda

tests/phpunit/data/helpers/WellProtectedClass.php
tests/phpunit/includes/TestingAccessWrapper.php
tests/phpunit/includes/TestingAccessWrapperTest.php

index a45cfbb..f2b5a14 100644 (file)
@@ -17,9 +17,20 @@ class WellProtectedParentClass {
 }
 
 class WellProtectedClass extends WellProtectedParentClass {
+       protected static $staticProperty = 'sp';
+       private static $staticPrivateProperty = 'spp';
+
        protected $property;
        private $privateProperty;
 
+       protected static function staticMethod() {
+               return 'sm';
+       }
+
+       private static function staticPrivateMethod() {
+               return 'spm';
+       }
+
        public function __construct() {
                parent::__construct();
                $this->property = 1;
index 63d8971..7332e15 100644 (file)
  *    $formatter = $title->getTitleFormatter();
  *
  * TODO:
- * - Provide access to static methods and properties.
  * - Organize other helper classes in tests/testHelpers.inc into a directory.
  */
 class TestingAccessWrapper {
+       /** @var mixed The object, or the class name for static-only access */
        public $object;
 
        /**
         * Return the same object, without access restrictions.
         */
        public static function newFromObject( $object ) {
+               if ( !is_object( $object ) ) {
+                       throw new InvalidArgumentException( __METHOD__ . ' must be called with an object' );
+               }
                $wrapper = new TestingAccessWrapper();
                $wrapper->object = $object;
                return $wrapper;
        }
 
+       /**
+        * Allow access to non-public static methods and properties of the class.
+        * Use non-static access,
+        */
+       public static function newFromClass( $className ) {
+               if ( !is_string( $className ) ) {
+                       throw new InvalidArgumentException( __METHOD__ . ' must be called with a class name' );
+               }
+               $wrapper = new TestingAccessWrapper();
+               $wrapper->object = $className;
+               return $wrapper;
+       }
+
        public function __call( $method, $args ) {
+               $methodReflection = $this->getMethod( $method );
+
+               if ( $this->isStatic() && !$methodReflection->isStatic() ) {
+                       throw new DomainException( __METHOD__ . ': Cannot call non-static when wrapping static class' );
+               }
+
+               return $methodReflection->invokeArgs( $methodReflection->isStatic() ? null : $this->object,
+                       $args );
+       }
+
+       public function __set( $name, $value ) {
+               $propertyReflection = $this->getProperty( $name );
+
+               if ( $this->isStatic() && !$propertyReflection->isStatic() ) {
+                       throw new DomainException( __METHOD__ . ': Cannot set property when wrapping static class' );
+               }
+
+               $propertyReflection->setValue( $this->object, $value );
+       }
+
+       public function __get( $name ) {
+               $propertyReflection = $this->getProperty( $name );
+
+               if ( $this->isStatic() && !$propertyReflection->isStatic() ) {
+                       throw new DomainException( __METHOD__ . ': Cannot get property when wrapping static class' );
+               }
+
+               return $propertyReflection->getValue( $this->object );
+       }
+
+       private function isStatic() {
+               return is_string( $this->object );
+       }
+
+       /**
+        * Return a property and make it accessible.
+        * @param string $name
+        * @return ReflectionMethod
+        */
+       private function getMethod( $name ) {
                $classReflection = new ReflectionClass( $this->object );
-               $methodReflection = $classReflection->getMethod( $method );
+               $methodReflection = $classReflection->getMethod( $name );
                $methodReflection->setAccessible( true );
-               return $methodReflection->invokeArgs( $this->object, $args );
+               return $methodReflection;
        }
 
        /**
+        * Return a property and make it accessible.
+        *
         * ReflectionClass::getProperty() fails if the private property is defined
         * in a parent class. This works more like ReflectionClass::getMethod().
+        *
+        * @param string $name
+        * @return ReflectionProperty
+        * @throws ReflectionException
         */
        private function getProperty( $name ) {
                $classReflection = new ReflectionClass( $this->object );
                try {
-                       return $classReflection->getProperty( $name );
+                       $propertyReflection = $classReflection->getProperty( $name );
                } catch ( ReflectionException $ex ) {
                        while ( true ) {
                                $classReflection = $classReflection->getParentClass();
@@ -54,23 +116,13 @@ class TestingAccessWrapper {
                                        continue;
                                }
                                if ( $propertyReflection->isPrivate() ) {
-                                       return $propertyReflection;
+                                       break;
                                } else {
                                        throw $ex;
                                }
                        }
                }
-       }
-
-       public function __set( $name, $value ) {
-               $propertyReflection = $this->getProperty( $name );
                $propertyReflection->setAccessible( true );
-               $propertyReflection->setValue( $this->object, $value );
-       }
-
-       public function __get( $name ) {
-               $propertyReflection = $this->getProperty( $name );
-               $propertyReflection->setAccessible( true );
-               return $propertyReflection->getValue( $this->object );
+               return $propertyReflection;
        }
 }
index fc54afa..23eb023 100644 (file)
@@ -3,6 +3,7 @@
 class TestingAccessWrapperTest extends MediaWikiTestCase {
        protected $raw;
        protected $wrapped;
+       protected $wrappedStatic;
 
        function setUp() {
                parent::setUp();
@@ -10,12 +11,38 @@ class TestingAccessWrapperTest extends MediaWikiTestCase {
                require_once __DIR__ . '/../data/helpers/WellProtectedClass.php';
                $this->raw = new WellProtectedClass();
                $this->wrapped = TestingAccessWrapper::newFromObject( $this->raw );
+               $this->wrappedStatic = TestingAccessWrapper::newFromClass( 'WellProtectedClass' );
+       }
+
+       /**
+        * @expectedException InvalidArgumentException
+        */
+       function testConstructorException() {
+               TestingAccessWrapper::newFromObject( 'WellProtectedClass' );
+       }
+
+       /**
+        * @expectedException InvalidArgumentException
+        */
+       function testStaticConstructorException() {
+               TestingAccessWrapper::newFromClass( new WellProtectedClass() );
        }
 
        function testGetProperty() {
                $this->assertSame( 1, $this->wrapped->property );
                $this->assertSame( 42, $this->wrapped->privateProperty );
                $this->assertSame( 9000, $this->wrapped->privateParentProperty );
+               $this->assertSame( 'sp', $this->wrapped->staticProperty );
+               $this->assertSame( 'spp', $this->wrapped->staticPrivateProperty );
+               $this->assertSame( 'sp', $this->wrappedStatic->staticProperty );
+               $this->assertSame( 'spp', $this->wrappedStatic->staticPrivateProperty );
+       }
+
+       /**
+        * @expectedException DomainException
+        */
+       function testGetException() {
+               $this->wrappedStatic->property;
        }
 
        function testSetProperty() {
@@ -30,6 +57,33 @@ class TestingAccessWrapperTest extends MediaWikiTestCase {
                $this->wrapped->privateParentProperty = 12;
                $this->assertSame( 12, $this->wrapped->privateParentProperty );
                $this->assertSame( 12, $this->raw->getPrivateParentProperty() );
+
+               $this->wrapped->staticProperty = 'x';
+               $this->assertSame( 'x', $this->wrapped->staticProperty );
+               $this->assertSame( 'x', $this->wrappedStatic->staticProperty );
+
+               $this->wrapped->staticPrivateProperty = 'y';
+               $this->assertSame( 'y', $this->wrapped->staticPrivateProperty );
+               $this->assertSame( 'y', $this->wrappedStatic->staticPrivateProperty );
+
+               $this->wrappedStatic->staticProperty = 'X';
+               $this->assertSame( 'X', $this->wrapped->staticProperty );
+               $this->assertSame( 'X', $this->wrappedStatic->staticProperty );
+
+               $this->wrappedStatic->staticPrivateProperty = 'Y';
+               $this->assertSame( 'Y', $this->wrapped->staticPrivateProperty );
+               $this->assertSame( 'Y', $this->wrappedStatic->staticPrivateProperty );
+
+               // don't rely on PHPUnit to restore static properties
+               $this->wrapped->staticProperty = 'sp';
+               $this->wrapped->staticPrivateProperty = 'spp';
+       }
+
+       /**
+        * @expectedException DomainException
+        */
+       function testSetException() {
+               $this->wrappedStatic->property = 1;
        }
 
        function testCallMethod() {
@@ -44,9 +98,22 @@ class TestingAccessWrapperTest extends MediaWikiTestCase {
                $this->wrapped->incrementPrivateParentPropertyValue();
                $this->assertSame( 9001, $this->wrapped->privateParentProperty );
                $this->assertSame( 9001, $this->raw->getPrivateParentProperty() );
+
+               $this->assertSame( 'sm', $this->wrapped->staticMethod() );
+               $this->assertSame( 'spm', $this->wrapped->staticPrivateMethod() );
+               $this->assertSame( 'sm', $this->wrappedStatic->staticMethod() );
+               $this->assertSame( 'spm', $this->wrappedStatic->staticPrivateMethod() );
        }
 
        function testCallMethodTwoArgs() {
                $this->assertSame( 'two', $this->wrapped->whatSecondArg( 'one', 'two' ) );
        }
+
+       /**
+        * @expectedException DomainException
+        */
+       function testCallMethodException() {
+               $this->wrappedStatic->incrementPropertyValue();
+       }
+
 }