Mockery1.0 テストダブル作成

テストダブル作成

Mockeyのメインゴールは、テストダブル(代替物)の作成を手伝うことです。スタブやモック、スパイを作成できます。

スタブとモックは同じものが生成されます。2つの違いは、スタブが呼び出し時に指定した結果を返すだけなのに対し、モックは受け取るのを期待しているメソッド呼び出しのエクスペクションを指定できる必要があります。

スパイは受け取った呼び出しの記録を保持し、後ほど呼び出し結果を調査できるようにしてくれるテストダブルのタイプです。

テストダブルオブジェクトの作成時、テストダブルの名前として識別子を渡すことができます。識別子を渡さないと、そのテストダブルの名前は不明(unknown)となります。さらに、識別子はクラス名である必要はありません。グッドプラクティスであり、私達がおすすめしているのは、常にテストダブルの対象のクラスと同じ名前を付けることです。

テストダブルに存在しているクラスと同じ名前を使用すると、そのテストダブルは(継承により)そのクラスのタイプを継承します。たとえば、モックオブジェクトは存在しているクラスのタイプヒントや、instanceofの評価を渡します。これは私達のコードが持つ期待を満足させるために、テストダブルが特定のタイプである必要がある場合に便利です。

スタブとモック

スタブとモックは、\Mockery::mock()メソッドを呼び出し、生成します。以下の例では、"foo"という名前のスタブ、もしくはモックをどのように作成するかを示しています。

$mock = \Mockery::mock('foo');

このように生成されたモックオブジェクトは、最もゆるい形態のモックで、\Mockery\MockInterfaceのインスタンスです。

Note: スタブ、モック、スパイにかかわらず、Mockeryにより生成されるテストダブルは、\Mockery\MockInterfaceのインスタンスです。

名前を持たないスタブやモックを生成するには、引数を付けずにmock()を呼び出してください。

$mock = \Mockery::mock();

以前説明したように、無名のモックオブジェクトを生成することは推奨していません。

クラス、抽象クラス、インターフェイス

スタブやモックオブジェクトを生成するために推奨するのは、テストダブルを作成する対象の実クラス名を使用する方法です。

$mock = \Mockery::mock('MyClass');

このスタブ/モックオブジェクトは、継承によりMyClassのタイプを保ちます。

スタブとモックオブジェクトは、具象クラス、抽象クラス、そしてインターフェイスでさえもベースにできます。一番の目的はタイプヒントに合致させるために、特定のタイプを確実にモックオブジェクトへ継承させることです。

$mock = \Mockery::mock('MyInterface');

このスタブ/モックオブジェクトは、MyInterfaceインターフェイスを実装します。

Note: finalを指定したクラス、もしくはfinalを指定したメソッドを持つクラスは、完全にモックすることはできません。こうしたクラスに対して、Mockeryはパーシャル(部分)モックの作成をサポートしています。パーシャルモックについては、ドキュメントで後ほど解説します。

Mockeryは、一つのクラスで複数のインターフェイスを実装するクラスに基づいたスタブ/モックの生成もサポートしています。\Mockery::mock()メソッドの最初の引数として、クラスとインターフェイスをコンマ区切りのリストで指定してください。

$mock = \Mockery::mock('MyClass, MyInterface, OtherInterface');

これでこのスタブは、MyClassのタイプを持ち、MyInterfaceOtherInterfaceインターフェイスを実装します。

Note: リストの最初の項目であるクラス名は必須ではありませんが、指定したほうが読みやすくフレンドリーでしょう。

モックへインターフェイスのリストを第2引数として渡し、実装することもできます。

$mock = \Mockery::mock('MyClass', 'MyInterface, OtherInterface');

これは直前の例と全く同じ、意図と目的です。

スパイ

Mockeryがサポートする、テストダブルの3つ目のタイプはスパイです。スパイとモックオブジェクトの主な違いは、スパイはテストダブルに対して行われた呼び出しの検査を呼び出し後に確認できることです。スパイはオブジェクトに対して行われる呼び出しの全てを確認する必要はない場合に使用します。

スパイは受け取ったメソッド呼び出し全てに対して、nullを返します。メソッド実行の戻り値をスパイに指定することはできません。そうしたいのであれば、スパイではなくモックオブジェクトを使用してください。

スパイを作成するには、\Mockery::spy()メソッドを呼び出します。

$spy = \Mockery::spy('MyClass');

スタブ/モックと同様に、具象クラス、抽象クラスをベースにするか、好きな数のインターフェイスを実装するように、Mockeryへ指示できます。

$spy = \Mockery::spy('MyClass, MyInterface, OtherInterface');

このスパイは、これによりMyClassのタイプで、MyInterfaceOtherInterfaceを実装しました。

Note: \Mockery::spy()メソッドコールは実際には、\Mockery::mock()->shouldIgnoreMissing()呼び出しの短縮形です。shouldIgnoreMissingメソッドは、「振る舞いのモディファイヤ(変更指示)」です。すぐ後に説明します。

モック vs. スパイ

以下の例を使用し、モックとスパイの違いを説明しましょう。

$mock = \Mockery::mock('MyClass');
$spy = \Mockery::spy('MyClass');

$mock->shouldReceive('foo')->andReturn(42);

$mockResult = $mock->foo();
$spyResult = $spy->foo();

$spy->shouldHaveReceived()->foo();

var_dump($mockResult); // int(42)
var_dump($spyResult); // null

この例でわかるように、モックオブジェクトでは呼び出す前に呼び出しのエクスペクションを指定しており、返ってくることを期待している結果を得ています。一方のスパイオブジェクトは、実行された呼び出しを後ほど確認しています。スパイに対するメソッドコールは常にnullを返します。

スパイ専用の章もご覧ください。

パーシャルテストダブル

パーシャル(部分)ダブルは、スタブのメソッドにエクスペクションを指定する、もしくはクラスの*いくつか*のメソッドをスパイするが、他のメソッドに関しては実際のコードを実行したい場合に便利です。

パーシャルテストダブルは、3つに分けています。

  • ランタイムパーシャルテストダブル
  • 生成パーシャルテストダブル
  • プロキシパーシャルテストダブル

ランタイムパーシャルテストダブル

ランタイムパーシャルと呼ぶ場合、テストダブルを作成し、それからそれをパーシャルに指定することを意味します。許可(allow)か期待(expect)するようにダブルへ指示していないメソッド呼び出しは、全てオブジェクトの通常のインスタンス上で実行されます。

class Foo {
    function foo() { return 123; }
    function bar() { return $this->foo(); }
}

$foo = mock(Foo::class)->makePartial();
$foo->foo(); // int(123);

他のMockeryダブルと同様に、このテストダブルに呼び出しの許可や期待を指定できます。

$foo->shouldReceive('foo')->andReturn(456);
$foo->bar(); // int(456)

ランタイムパーシャルテストダブルの使用例は、クックブックの大きなParentクラスページで確認してください。

生成パーシャルテストダブル

生成可能な2つ目のパーシャルダブルタイプは、生成パーシャルと呼んでおり、Mockeryにどのメソッドが許可/期待できるのかを指定したものです。指定外のメソッドでは、 直接 実際のコードが実行されるため、スタブやメソッドに対するエクスペクションは動作しません。

class Foo {
    function foo() { return 123; }
    function bar() { return $this->foo(); }
}

$foo = mock("Foo[foo]");

$foo->foo(); // エラー、エクスペクションを指定していない

$foo->shouldReceive('foo')->andReturn(456);
$foo->foo(); // int(456)

// setting an expectation for this has no effect
$foo->shouldReceive('bar')->andReturn(999);
$foo->bar(); // int(456)

 

Note: 生成パーシャルテストダブルをサポートしていますが、この機能の使用は推奨していません。

理由の一つは、生成パーシャルはモックしようとするオリジナルクラスのコンストラクターを呼び出してしまうからです。これにより、アプリケーションコードをテストするとき、思いもしない副作用が生まれることでしょう。

詳細については、オリジナルのコンストラクターを呼び出さないを参照してください。

プロキシパーシャルテストダブル

パーシャルの最後の種類は、プロキシパーシャルモックです。finalが指定されているためモックにできないクラスに遭遇することがあります。似たようなケースで、finalが指定されているメソッドを持つクラスに出会うことがあります。このようなシナリオではモックするため、シンプルにクラスを拡張することも、メソッドをオーバーライドすることもできません。クリエイティブに解決する必要があります。

$mock = \Mockery::mock(new MyClass);

そうです、この新しいモックはプロキシです。呼び出しを横取りし、エクスペクションが指定されていないメソッド呼び出しは、(生成し、mockメソッドに渡した)仲介するオブジェクトへ渡し直します。これにより間接的にfinalのメソッドをモックできます。なぜなら、プロキシは制約を受けないからです。トレードオフは明確です。プロキシパーシャルはモックしようとしているクラスのタイプヒントのチェックに失敗します。なぜなら、そのクラスを拡張できないからです。

エイリアス

(まだロードされていない)クラスの名前に、"alias:"をプレフィックスとして付けると、「エイリアスモック」が生成されます。エイリアスモックは、指定したクラス名でstdClassのクラスエイリアスを作成します。このエイリアスはpublicの静的メソッドをモックできるようにするために使用します。新しいモックオブジェクトへ指定される、静的メソッドを参照するエクスペクションは、このクラスへの全静的呼び出しにより使用されます。

$mock = \Mockery::mock('alias:MyClass');

 

Note: クラスのエイリアスをサポートしていますが、推奨していません。

オーバーロード

(まだロードされていない)有効なクラス名へ"overload:"をプリフィックスとして付けると、("alias:"と同様に)エイリアスモックを生成します。違いは、そのクラスの新しいインスタンスが生成され、オリジナルのモック($mock)に指定されたエクスペクションを全てインポートすることです。オリジナルのモックは新しいインスタンスへエクスペクションを保存するために使用されるため、検査されることはありません。シンプルな「エイリアスモック」と区別するために、「インスタンスモック」という言葉を使用しています。

言い換えれば、モックしたクラスの新しいインスタンスが生成されて時に、インスタンスモックは「横取り」し、モックが代わりに使用されます。これは特に、後ほど説明する依存が強い場合のモックで便利です。

$mock = \Mockery::mock('overload:MyClass');

 

Note: 2つ以上のテスト間で、エイリアス/インスタンスモックを使用すると、同じ名前の2つのクラスは持てないため、fatalエラーが発生します。これを防ぐには、この種のテストは、独立したPHPプロセスで実行してください。PHPUnitとPHPTで、サポートされています。

名前付きモック

namedMock()は最初の引数により呼び出されるクラスを生成します。下記の例の場合は、MyClassNameです。残りの引数は、mockメソッドと同じ取り扱いです。

$mock = \Mockery::namedMock('MyClassName', 'DateTime');

この例では、DateTimeを拡張した、MyClassNameという名前のクラスが生成されます。

名前付きモックが使用されるのは極めてまれですが、コードが__CLASS__マジック定数に依存しているか、一つの抽象クラスから派生した、実際には別の2つのクラスが必要な場合に役立ちます。

名前付きモックの使用例は、クックブックの章のクラス定数をご覧ください。

 

Note: 名前付きモックは一度のみ生成でき、以降に別の引数でnamedMockを呼び出すと例外が発生します。

コンストラクターの引数

モックするクラスが、コンストラクタ引数を必要とする場合が時々あります。2つ目の引数にインデックスされた配列としてMockeryへ渡すことができます。

$mock = \Mockery::mock('MyClass', [$constructorArg1, $constructorArg2]);

もし、同時にMyClassがインターフェイスを実装する場合は、第3引数として渡します。

$mock = \Mockery::mock('MyClass', 'MyInterface', [$constructorArg1, $constructorArg2]);

これで、Mockeryは$constructorArg1$constructorArg2をコンストラクターへ渡すことがわかります。

振る舞いモディファイヤー

モックオブジェクトを作成する際、Mockeryのデフォルトの振る舞いではなく、一般的によく使用される振る舞いにしたい場合があります。

shouldIgnoreMissing()振る舞いモディファイヤーを使用すると、このモックはパッシブモックであるとラベル付けることができます。

\Mockery::mock('MyClass')->shouldIgnoreMissing();

このようなモックオブジェクトでは、エクスペクションでカバーされていないメソッドの呼び出し時に、一致するエクスペクションが見つからない通常のエラーの代わりに、nullを返します。

PHP7.0.0以降では、エクスペクションが見つからず、返すタイプを持つメソッドは、(返すタイプがクラスの場合)オブジェクトのモックか、空文字列や空配列、整数や実数ではゼロ、論理値ではfalse、もしくは空のクロージャーのような「偽」にあたるプリミティブな値のどれかを返します。

PHP7.1.0以降では、エクスペクションが見つからないメソッドで、返すタイプがnullableの場合,nullを返します。

(たとえば、nullオブジェクトの)\Mockery\Undefinedタイプのオブジェクトを返したい場合(バージョン0.7.2での振る舞い)は、追加のモディファイヤーを使用することもできます。

\Mockery::mock('MyClass')->shouldIgnoreMissing()->asUndefined();

返されるオブジェクトはプレースホルダー(代用品)でしかないため、使うべきでない場所で使用する間違った使い方を行えば、運命の導きにより、ロジックチェックをパスしないでしょう。

makePartial()メソッドは、ランタイムパーシャルテストダブルを生成するメソッドとして、既に説明しました。

\Mockery::mock('MyClass')->makePartial();

この形式のモックオブジェクトは、エクスペクションの対象外のメソッドをモックの親クラス、この例の場合はMyClassへ引き渡します。前のshouldIgnoreMissing()nullを返していましたが、この振る舞いは単に一致する親のメソッドを呼び出すだけです。

ドキュメント章別ページ

概論

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

バージョン

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる