4 * Test that a factory class correctly forwards all arguments to the class it constructs. This is
5 * useful because sometimes a class' constructor will have more arguments added, and it's easy to
6 * accidentally have the factory's constructor fall out of sync.
8 trait FactoryArgTestTrait
{
10 * @return string Name of factory class
12 abstract protected static function getFactoryClass();
15 * @return string Name of instance class
17 abstract protected static function getInstanceClass();
20 * @return int The number of arguments that the instance constructor receives but the factory
21 * constructor doesn't. Used for a simple argument count check. Override if this isn't zero.
23 protected static function getExtraClassArgCount() {
28 * Override if your factory method name is different from newInstanceClassName.
32 protected function getFactoryMethodName() {
33 return 'new' . $this->getInstanceClass();
37 * Override if $factory->$method( ...$args ) isn't the right way to create an instance, where
38 * $method is returned from getFactoryMethodName(), and $args is constructed by applying
39 * getMockValueForParam() to the factory method's parameters.
41 * @param object $factory Factory object
42 * @return object Object created by factory
44 protected function createInstanceFromFactory( $factory ) {
45 $methodName = $this->getFactoryMethodName();
46 $methodObj = new ReflectionMethod( $factory, $methodName );
48 foreach ( $methodObj->getParameters() as $param ) {
49 $mocks[] = $this->getMockValueForParam( $param );
52 return $factory->$methodName( ...$mocks );
55 public function testConstructorArgNum() {
56 $factoryClass = static::getFactoryClass();
57 $instanceClass = static::getInstanceClass();
58 $factoryConstructor = new ReflectionMethod( $factoryClass, '__construct' );
59 $instanceConstructor = new ReflectionMethod( $instanceClass, '__construct' );
61 $instanceConstructor->getNumberOfParameters() - static::getExtraClassArgCount(),
62 $factoryConstructor->getNumberOfParameters(),
63 "$instanceClass and $factoryClass constructors have an inconsistent number of " .
64 ' parameters. Did you add a parameter to one and not the other?' );
68 * Override if getMockValueForParam doesn't produce suitable values for one or more of the
69 * parameters to your factory constructor or create method.
71 * @param ReflectionParameter $param One of the factory constructor's arguments
72 * @return array Empty to not override, or an array of one element which is the value to pass
73 * that will allow the object to be constructed successfully
75 protected function getOverriddenMockValueForParam( ReflectionParameter
$param ) {
80 * Override if this doesn't produce suitable values for one or more of the parameters to your
81 * factory constructor or create method.
83 * @param ReflectionParameter $param One of the factory constructor's arguments
84 * @return mixed A value to pass that will allow the object to be constructed successfully
86 protected function getMockValueForParam( ReflectionParameter
$param ) {
87 $overridden = $this->getOverriddenMockValueForParam( $param );
89 return $overridden[0];
92 $pos = $param->getPosition();
94 $type = (string)$param->getType();
96 if ( $type === 'array' ) {
97 return [ "some unlikely string $pos" ];
100 if ( class_exists( $type ) ||
interface_exists( $type ) ) {
101 return $this->createMock( $type );
104 if ( $type === '' ) {
105 // Optimistically assume a string is okay
106 return "some unlikely string $pos";
109 $this->fail( "Unrecognized parameter type $type" );
113 * Assert that the given $instance correctly received $val as the value for parameter $name. By
114 * default, checks that the instance has some member whose value is the same as $val.
116 * @param object $instance
117 * @param string $name Name of parameter to the factory object's constructor
120 protected function assertInstanceReceivedParam( $instance, $name, $val ) {
121 foreach ( ( new ReflectionObject( $instance ) )->getProperties() as $prop ) {
122 $prop->setAccessible( true );
123 if ( $prop->getValue( $instance ) === $val ) {
124 $this->assertTrue( true );
129 $this->assertFalse( true, "Param $name not received by " . static::getInstanceClass() );
132 public function testAllArgumentsWerePassed() {
133 $factoryClass = static::getFactoryClass();
135 $factoryConstructor = new ReflectionMethod( $factoryClass, '__construct' );
137 foreach ( $factoryConstructor->getParameters() as $param ) {
138 $mocks[$param->getName()] = $this->getMockValueForParam( $param );
142 $this->createInstanceFromFactory( new $factoryClass( ...array_values( $mocks ) ) );
144 foreach ( $mocks as $name => $mock ) {
145 $this->assertInstanceReceivedParam( $instance, $name, $mock );