PHP
PHPUnit
テスト
laravel
mockery

PHPUnitのテストコードを書くときによく使うイディオム

0. 前提ライブラリ

1. 例外が発生することをテストする

/**
 * @test
 * @expectedException ExceptionA
 */
public function 例外が発生することをテストする()
{
    $classA = new ClassA();
    $classA->method1ClassA();
}

2. 例外が発生しないことをテストする

/**
 * @test
 */
public function 例外が発生しないことをテストする()
{
    $classA = new ClassA();
    try {
        $classA->method1ClassA();
    } catch (ExceptionA $e) {
        $this->fail();
    }
    $this->assertTrue(true);
}

3. publicメソッドのモックを作ってテストする

/**
 * @test
 */
public function publicメソッドのモックを作ってテストする()
{
   $mockClassB = Mockery::mock('ClassB');
   $mockClassB->shouldReceive('method1ClassB')->andReturn('some value');
   $classA = new ClassA($mockClassB);
   $classA->method1ClassA();
}

4. publicプロパティのモックを作ってテストする

/**
 * @test
 */
public function publicプロパティのモックを作ってテストする()
{
    $mockClassB = Mockery::mock('ClassB');
    $mockClassB->property1ClassB = 'some value';
    $classA = new ClassA($mockClassB);
    $classA->method1ClassA();
}

5. public staticメソッドのモックを作ってテストする

/**
 * @test
 * @runInSeparateProcess
 * @preserveGlobalState disabled
 */
public function public_staticメソッドのモックを作ってテストする()
{
    $mockClassB = Mockery::mock('alias:ClassB');
    $mockClassB->shouldReceive('method1ClassB')->andReturn('some value');
    $classA = new ClassA();
    $classA->method1ClassA();
}

6. publicメソッドのモックを作ってテストする(クラス名がハードコーディングされている場合)

/**
 * @test
 * @runInSeparateProcess
 * @preserveGlobalState disabled
 */
public function publicメソッドのモックを作ってテストするクラス名がハードコーディングされている場合()
{
    $mockClassB = Mockery::mock('overload:ClassB');
    $mockClassB->shouldReceive('method1ClassB')->andReturn('some value');
    $classA = new ClassA();
    $classA->method1ClassA();
}

7. 自クラスのpublicメソッドのモックを作ってテストする

/**
 * @test
 */
public function 自クラスのpublicメソッドのモックを作ってテストする()
{
    $partialMockClassA = Mockery::mock('ClassA')->makePartial();
    $partialMockClassA->shouldReceive('method2ClassA')->andReturn('some value');
    $partialMockClassA->method1ClassA();
}

例7-1. 依存クラスのコンストラクタ呼び出しをモックしてテストする

class ClassA
{
    public function method1ClassA()
    {
        $classB = $this->newClassB();
    }

    /**
     * コンストラクタのモックは作れないのでインスタンス生成処理をラップする
     */
    public function newClassB()
    {
        return new ClassB();
    }
}
/**
 * @test
 */
public function 依存クラスのコンストラクタ呼び出しをモックしてテストする()
{
    $mockClassB = Mockery::mock('ClassB');
    $partialMockClassA = Mockery::mock('ClassA')->makePartial();
    $partialMockClassA->shouldReceive('newClassB')->andReturn($mockClassB);
    $partialMockClassA->method1ClassA();
}

8. チェインしたpublicメソッドのモックを作ってテストする

/**
 * @test
 */
public function チェインしたpublicメソッドのモックを作ってテストする()
{
    $mockClassB = Mockery::mock('ClassB');
    $mockClassB->shouldReceive('method1ClassB->method2ClassB')->andReturn('some value');
    $classA = new ClassA($mockClassB);
    $classA->method1ClassA();
}

9. [Laravel] ファサードのpublicメソッドのモックを作ってテストする

/**
 * @test
 */
public function ファサードのpublicメソッドのモックを作ってテストする()
{
    FacadeA::shouldReceive('method1FacadeA')->andReturn('some value');
    $classA = new ClassA();
    $classA->method1ClassA();
}

10. [Laravel] Eloquentのpublicメソッドのモックを作ってテストする

/**
 * @test
 */
public function Eloquentのpublicメソッドのモックを作ってテストする()
{
    $mockEloquentA = Mockery::mock('EloquentA');
    $mockEloquentA->shouldReceive('method1EloquentA')->andReturn('some value');
    $classA = new ClassA($mockEloquentA);
    $classA->method1ClassA();
}

11. [Laravel] Eloquentのpublicプロパティのモックを作ってテストする

/**
 * @test
 */
public function Eloquentのpublicプロパティのモックを作ってテストする()
{
    $mockEloquentA = Mockery::mock('EloquentA')->makePartial();
    $mockEloquentA->property1EloquentA = 'some value';
    $classA = new ClassA($mockEloquentA);
    $classA->method1ClassA();
}

12. private/protectedメソッドをテストする

/**
 * @test
 */
public function privateメソッドをテストする()
{
    $classA = new ClassA();
    $reflectionMethod1ClassA = new ReflectionMethod($classA, 'method1ClassA');
    $reflectionMethod1ClassA->setAccessible(true);
    $reflectionMethod1ClassA->invoke($classA);
}

13. private/protectedプロパティの値を書き換えてテストする

/**
 * @test
 */
public function privateプロパティの値を書き換えてテストする()
{
    $classA = new ClassA();
    $reflectionClassA = new ReflectionClass($classA);
    $reflectionProperty1ClassA = $reflectionClassA->getProperty('property1ClassA');
    $reflectionProperty1ClassA->setAccessible(true);
    $reflectionProperty1ClassA->setValue($classA, 'some value');
    $classA->method1ClassA();
}

14. private/protectedプロパティの値をテストする

/**
 * @test
 */
public function privateプロパティの値をテストする()
{
    $classA = new ClassA();
    $classA->method1ClassA();
    $reflectionClassA = new ReflectionClass($classA);
    $reflectionProperty1ClassA = $reflectionClassA->getProperty('property1ClassA');
    $reflectionProperty1ClassA->setAccessible(true);
    $valueReflectionProperty1ClassA = $reflectionProperty1ClassA->getValue($classA);
    $this->assertEquals('some value', $valueReflectionProperty1ClassA);
}

15. 仮想ファイルを使ってファイルの読み書きをテストする

/**
 * @test
 */
public function 仮想ファイルを使ってファイルの読み書きをテストする()
{
    $root = vfsStream::setup('root');                                          
    vfsStream::newFile('file')->at($root)->setContent('some contents');                                                                                                          
    $path = vfsStream::url('root/file');                                       
    $classA = new ClassA($path);                                                    
    $classA->method1ClassA();  
}

16. 無名クラスを使ってトレイトをテストする

/**
 * @test
 */
public function 無名クラスを使ってトレイトをテストする()
{
    $classUsingTrait = new class($property) {
        use TraitA;

        public function __construct($property)
        {
             $this->method1TraitA($property);
        }
    };
    $classUsingTrait->method2TraitA();
}