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


# 0. 前提ライブラリ

- [Mockery](https://github.com/mockery/mockery)
- [vfsStream](https://github.com/mikey179/vfsStream)

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

## PHPUnit 8系

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

## PHPUnit 7系以下

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

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

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

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

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

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

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

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

```php
/**
 * @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メソッドのモックを作ってテストする（クラス名がハードコーディングされている場合）

```php
/**
 * @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メソッドのモックを作ってテストする

```php
/**
 * @test
 */
public function 自クラスのpublicメソッドのモックを作ってテストする()
{
    $partialMockClassA = Mockery::mock('ClassA')->makePartial();
    $partialMockClassA->shouldReceive('method2ClassA')->andReturn('some value');
    $partialMockClassA->method1ClassA();
}
```
 
## 例7-1. 依存クラスのコンストラクタ呼び出しをモックしてテストする

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

    /**
     * コンストラクタのモックは作れないのでインスタンス生成処理をラップする
     */
    public function newClassB()
    {
        return new ClassB();
    }
}
```

```php
/**
 * @test
 */
public function 依存クラスのコンストラクタ呼び出しをモックしてテストする()
{
    $mockClassB = Mockery::mock('ClassB');
    $partialMockClassA = Mockery::mock('ClassA')->makePartial();
    $partialMockClassA->shouldReceive('newClassB')->andReturn($mockClassB);
    $partialMockClassA->method1ClassA();
}
```

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

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

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

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

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

```php
/**
 * @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プロパティのモックを作ってテストする

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

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

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

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

```php
/**
 * @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プロパティの値をテストする

```php
/**
 * @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. 仮想ファイルを使ってファイルの読み書きをテストする

```php
/**
 * @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. 無名クラスを使ってトレイトをテストする

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

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

# 17. 未実装のテストをスキップする

```php
/**
 * @test
 */
public function 未実装のテストをスキップする()
{
    $this->markTestIncomplete('このテストはまだ実装されていません。'); 
}
```

# 18. アサーションがないテストをリスキーと見なさない

## PHPUnit 9系

```php
/**
 * @test
 */
public function アサーションがないテストをリスキーと見なさない()
{
    $this->expectNotToPerformAssertions();
    new ClassA();
}
```

## PHPUnit 8系以下

```php
/**
 * @test
 * @doesNotPerformAssertions
 */
public function アサーションがないテストをリスキーと見なさない()
{
    new ClassA();
}
```