やっと折り返し地点ですね。今回からは 3 回に渡る JustMock の公式ドキュメントにあるサンプルを実装するシリーズとなります。まずは、「Final Mocking samples」を Prig に移行してみましょう。
以下の記事、ライブラリを使用/参考にさせていただいています。この場を借りてお礼申し上げます m(_ _)m
Using Jekyll with Pages - GitHub Help
Can I share a Microsoft Fakes unit test with a developer using Visual Studio Professional - Stack Overflow
shim a sealed class singleton method and with MS Fakes - Stack Overflow
Search - GitHub Anonymously Hosted DynamicMethods Assembly
API Hooking with MS Detours - CodeProject
Incorrect solution build ordering when using MSBuild.exe - The Visual Studio Blog - Site Home - MSDN Blogs
visual studio 2010 - How do I target a specific .NET project within a Solution using MSBuild from VS2010 - Stack Overflow
Advanced Usage | JustMock Documentation
.net - Is there any free mocking framework that can mock static methods and sealed classes - Stack Overflow
Modern ASP.NET Webskills by @calebjenkins
目次
- 準備
- Assert Final Method Setup
- Assert Property Get
- Assert Property Set
- Assert Method Overloads
- Assert Method Callbacks
- Assert Generic Types and Methods
準備
このサンプルでは、シリーズということもあり、1 つのソリューションに複数のプロジェクトを追加するという構成を採っています。


なお、この例は、間接的に呼び出される対象が、GAC に登録されていない Assembly に含まれており、以前の例(Moles、Fakes)とは異なることに注意してくださいね。
これまでは Package Manager Console で間接スタブを作成する手順を実施しても問題ありませんでしたが、今後は、PowerShell(コンソール)を使えるようになることを推奨します。Package Manager Console は、仕様上、ネストしたプロンプトをサポートしていません。従って、1 度間接対象の解析を行うために Assembly を読み込むと、2 度とその Assembly を解放できないという問題があるのです。そもそも、いつも使う機能である、オートコンプリート、コマンド履歴なども PowerShell(コンソール)のものより機能的に劣ります。間接設定の追加やテストの実行時は Package Manager Console を使い、Assembly の解析時は PowerShell(コンソール)を使うということが、効率が良いでしょう。
※注※:以下の解説で、PM> で始まるコマンドは Package Manager Console で実行しますが、PS> で始まるコマンドは PowerShell(コンソール)で実行することに注意してください。
FinalMockingMigration をビルド後、出力ディレクトリを開き、以下のコマンドを実行します。プロジェクトがいくつもありますので、対象のテストプロジェクトを Default project: として選択することをお忘れなく:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PM> dir | |
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo | |
Mode LastWriteTime Length Name | |
---- ------------- ------ ---- | |
d---- 2014/09/16 6:37 FinalMockingMigration | |
d---- 2014/09/16 20:30 FinalMockingMigrationTest | |
d---- 2014/09/16 6:35 packages | |
d---- 2014/09/16 20:32 SealedMockingMigration | |
d---- 2014/09/16 20:34 SealedMockingMigrationTest | |
d---- 2014/09/16 20:37 StaticMockingMigration | |
d---- 2014/09/16 20:40 StaticMockingMigrationTest | |
-a--- 2014/09/16 6:31 3665 JustMockMigrationDemo.sln | |
PM> cd .\FinalMockingMigration\bin\Debug | |
PM> dir | |
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\FinalMockingMigration\bin\Debug | |
Mode LastWriteTime Length Name | |
---- ------------- ------ ---- | |
-a--- 2014/09/16 6:38 6144 FinalMockingMigration.exe | |
-a--- 2014/09/16 6:30 189 FinalMockingMigration.exe.config | |
-a--- 2014/09/16 6:38 13824 FinalMockingMigration.pdb | |
-a--- 2014/09/17 6:31 23168 FinalMockingMigration.vshost.exe | |
-a--- 2014/09/16 6:30 189 FinalMockingMigration.vshost.exe.config | |
-a--- 2013/06/18 21:28 490 FinalMockingMigration.vshost.exe.manifest | |
PM> padd -af (dir .\FinalMockingMigration.exe).FullName | |
PM> | |
ちなみに、padd -af は、Add-PrigAssembly -AssemblyFrom のエイリアスです。GAC に登録されていない Assembly の間接スタブ設定を追加する場合、コマンドの引数にフルパスを引き渡す必要があることに注意してください。PowerShell(スクリプト言語)では、コマンド (dir <target file>).FullName でフルファイルパスを取得することができます。FinalMockingMigrationTest に FinalMockingMigration.v4.0.30319.v1.0.0.0.prig が追加されれば成功です。
さて、次は、FinalMockingMigration の出力ディレクトリ上で PowerShell(コンソール)を実行し、Assembly を解析しましょう。Windows 8 を使っているのであれば、エクスプローラで対象のディレクトリを表示中、Alt、F、R のキーコンビネーションが便利ですよね。現在の位置を確認したら、ネストしたプロンプトを開始します:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PS> $pwd | |
Path | |
---- | |
C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\FinalMockingMigration\bin\Debug | |
PS> powershell | |
Windows PowerShell | |
Copyright (C) 2013 Microsoft Corporation. All rights reserved. | |
PS> | |
Package Manager Console で利用が可能になっている種々のコマンド(Find-IndirectionTarget や Get-IndirectionStubSetting など)は、Prig をインストール後、モジュール $(SolutionDir)\packages\Prig.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PS> ipmo "C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\packages\Prig.1.0.0\tools\Urasandesu.Prig" | |
PS> gmo | |
ModuleType Version Name ExportedCommands | |
---------- ------- ---- ---------------- | |
Manifest 3.1.0.0 Microsoft.PowerShell.Management {Add-Computer, Add-Content, Checkpoint-Computer, Clear-Con... | |
Manifest 3.1.0.0 Microsoft.PowerShell.Utility {Add-Member, Add-Type, Clear-Variable, Compare-Object...} | |
Script 0.0.0.0 Urasandesu.Prig {Add-PrigAssembly, ConvertTo-PrigAssemblyName, Find-Indire... | |
PS> | |
GAC に登録していない Assembly を読み込みたい場合、System.Reflection.Assembly.LoadFrom(string) にフルパスを引き渡す必要があります。取得には・・・って、ちょっと前にやりましたね (^^ゞ:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PS> dir | |
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\FinalMockingMigration\bin\Debug | |
Mode LastWriteTime Length Name | |
---- ------------- ------ ---- | |
-a--- 2014/09/16 6:38 6144 FinalMockingMigration.exe | |
-a--- 2014/09/16 6:30 189 FinalMockingMigration.exe.config | |
-a--- 2014/09/16 6:38 13824 FinalMockingMigration.pdb | |
-a--- 2014/09/17 6:31 23168 FinalMockingMigration.vshost.exe | |
-a--- 2014/09/16 6:30 189 FinalMockingMigration.vshost.exe.config | |
-a--- 2013/06/18 21:28 490 FinalMockingMigration.vshost.exe.manifest | |
PS> (dir .\FinalMockingMigration.exe).FullName | |
C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\FinalMockingMigration\bin\Debug\FinalMockingMigration.exe | |
Assembly を読み込み後、変数に設定します。ところで、履歴から実行したコマンドを呼び出し、ちょっと変更して再実行するという流れは PowerShell(コンソール)の真骨頂だと思います。Package Manager Console で同じことをしようものなら、重複した履歴を延々と遡る必要があったり、オートコンプリートによる意図しない消去を食らったりすることになるでしょう。ストレスがマッハになること請け合いです (-_-;):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PS> [System.Reflection.Assembly]::LoadFrom((dir .\FinalMockingMigration.exe).FullName) | |
GAC Version Location | |
--- ------- -------- | |
False v4.0.30319 C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\FinalMockingMigrati... | |
PS> $asmInfo = [System.Reflection.Assembly]::LoadFrom((dir .\FinalMockingMigration.exe).FullName) | |
PS> | |
GetTypes で型を確認すると、上から 3 つが対象ということがわかります。それらに対し、前のサンプルと同様のフィルタを掛けてみましょう:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PS> $asmInfo.GetTypes() | |
IsPublic IsSerial Name BaseType | |
-------- -------- ---- -------- | |
True False Foo System.Object | |
False True EchoEventHandler System.MulticastDelegate | |
True False FooGeneric System.Object | |
False False Program System.Object | |
PS> ($asmInfo.GetTypes())[0..2] | % { $_.GetMembers() } | ? { $_ -is [System.Reflection.MethodBase] } | ? { !$_.IsAbstract } | ? { $_.DeclaringType -eq $_.ReflectedType } | |
Method | |
------ | |
Int32 Execute(Int32, Int32) | |
Int32 Execute(Int32) | |
Int32 Echo(Int32) | |
System.String get_FooProp() | |
Void set_FooProp(System.String) | |
Void add_OnEchoCallback(EchoEventHandler) | |
Void remove_OnEchoCallback(EchoEventHandler) | |
Void .ctor() | |
Void Invoke(Boolean) | |
System.IAsyncResult BeginInvoke(Boolean, System.AsyncCallback, System.Object) | |
Void EndInvoke(System.IAsyncResult) | |
Void .ctor(System.Object, IntPtr) | |
TRet Echo[T,TRet](T) | |
Void .ctor() | |
PS> | |
結果が良さそうであれば、間接スタブ設定に変換し、クリップボードにコピーします。なお、解析が終わった Assembly を解放したい場合は、ネストしたプロンプトを終了すれば OK です。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PS> ($asmInfo.GetTypes())[0..2] | % { $_.GetMembers() } | ? { $_ -is [System.Reflection.MethodBase] } | ? { !$_.IsAbstract } | ? { $_.DeclaringType -eq $_.ReflectedType } | pget | clip | |
PS> exit | |
PS> | |
Visual Studio に戻り、FinalMockingMigration.v4.0.30319.v1.0.0.0.prig に間接スタブ設定を貼り付けます。ビルドは通りましたか?それでは、ちょっと長くなりましたが、実際の移行を行っていきましょう!
Assert Final Method Setup
Fakes のサンプルでも言いましたが、Prig は Mock Object としての機能を持っていませんので、Moq のような別のモックフレームワークと一緒に使うことを推奨しています。しかし、どうも JustMock のサンプルは、単に Test Stub としての振る舞いが紹介されているだけのものが大半ですので、Prig のサンプルもそれと一貫性を持たせるようにしました:
はじめに、final メソッドの戻り値を入れ替えてみましょう:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Test] | |
public void Prig_should_setup_a_call_to_a_final_method() | |
{ | |
using (new IndirectionsContext()) | |
{ | |
// Arrange | |
var fooProxy = new PProxyFoo(); | |
fooProxy.EchoInt32().Body = (@this, arg1) => 10; | |
var foo = (Foo)fooProxy; | |
// Act | |
var actual = foo.Echo(1); | |
// Assert | |
Assert.AreEqual(10, actual); | |
} | |
} |
fooProxy.EchoInt32().Body = (@this, arg1) => 10; で、Prig は、その final メソッドを、定数 10 を返す処理に入れ替えています。大丈夫ですよね。次、行ってみましょう!
Assert Property Get
final プロパティを入れ替えるサンプルです:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Test] | |
public void Prig_should_setup_a_call_to_a_final_property() | |
{ | |
using (new IndirectionsContext()) | |
{ | |
// Arrange | |
var fooProxy = new PProxyFoo(); | |
fooProxy.FooPropGet().Body = @this => "bar"; | |
var foo = (Foo)fooProxy; | |
// Act | |
var actual = foo.FooProp; | |
// Assert | |
Assert.AreEqual("bar", actual); | |
} | |
} |
fooProxy.FooPropGet().Body = @this => "bar"; で、Prig は、その final プロパティを、定数 bar を返す処理に入れ替えています。どんどん行きますよ (^O^)
Assert Property Set
final プロパティのセッターを検証するサンプルです。これは Moq を使ったほうが簡単そうですね:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Test] | |
[ExpectedException(typeof(MockException))] | |
public void Prig_should_assert_property_set() | |
{ | |
using (new IndirectionsContext()) | |
{ | |
// Arrange | |
var fooProxy = new PProxyFoo(); | |
var fooPropSetMock = new Mock<IndirectionAction<Foo, string>>(MockBehavior.Strict); | |
fooPropSetMock.Setup(_ => _(fooProxy, "ping")); | |
fooProxy.FooPropSetString().Body = fooPropSetMock.Object; | |
var foo = (Foo)fooProxy; | |
// Act, Assert | |
foo.FooProp = "foo"; | |
} | |
} |
Telerik.JustMock.Behavior.Strict は、Moq.MockBehavior.Strict にあたります。従って、Setup で指定した条件を満たさない引数で対象のメンバーを呼び出した場合、MockException をスローするようになります。
Assert Method Overloads
各 final メソッドのオーバーロードを入れ替えてみましょう。間接スタブ設定を正しく追加していれば、特に迷うことは無いはずです:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Test] | |
public void Prig_should_assert_on_method_overload() | |
{ | |
using (new IndirectionsContext()) | |
{ | |
// Arrange | |
var fooProxy = new PProxyFoo(); | |
fooProxy.ExecuteInt32().Body = (@this, arg1) => arg1; | |
fooProxy.ExecuteInt32Int32().Body = (@this, arg1, arg2) => arg1 + arg2; | |
var foo = (Foo)fooProxy; | |
// Act, Assert | |
Assert.AreEqual(1, foo.Execute(1)); | |
Assert.AreEqual(2, foo.Execute(1, 1)); | |
} | |
} |
特に問題は無いですよね?次に進みましょう。
Assert Method Callbacks
イベントとしてのコールバックを置換することを説明する例です。Moles の例で紹介しましたが、実装には若干のテクニックが必要です:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Test] | |
public void Prig_should_assert_on_method_callbacks() | |
{ | |
using (new IndirectionsContext()) | |
{ | |
// Arrange | |
var handler = default(Foo.EchoEventHandler); | |
var fooProxy = new PProxyFoo(); | |
fooProxy.AddOnEchoCallbackEchoEventHandler().Body = (@this, value) => handler += value; | |
fooProxy.EchoInt32().Body = (@this, arg1) => { handler(true); return arg1; }; | |
var foo = (Foo)fooProxy; | |
var called = false; | |
foo.OnEchoCallback += echoed => called = echoed; | |
// Act | |
foo.Echo(10); | |
// Assert | |
Assert.IsTrue(called); | |
} | |
} |
実装は以下の流れになります:
- fooProxy.AddOnEchoCallbackEchoEventHandler().Body = (@this, value) => handler += value; のように、入れ替えたいイベントの += 演算子を乗っ取り、渡されたハンドラ(value)をテストのためのデリゲート(handler)に紐付ける。
- 元のイベントを発行したいタイミングで、手順 1 で紐づけたデリゲート(handler)を代わりに実行する。
Assert Generic Types and Methods
ジェネリックな型とメソッドを入れ替える例です:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Test] | |
public void Prig_should_assert_on_generic_types_and_method() | |
{ | |
using (new IndirectionsContext()) | |
{ | |
// Arrange | |
var expected = "ping"; | |
var fooGenericProxy = new PProxyFooGeneric(); | |
fooGenericProxy.EchoOfTOfTRetT<string, string>().Body = (@this, s) => s; | |
var fooGeneric = (FooGeneric)fooGenericProxy; | |
// Act | |
var actual = fooGeneric.Echo<string, string>(expected); | |
// Assert | |
Assert.AreEqual(expected, actual); | |
} | |
} |
ね、簡単でしょう?これに関しては、以前の記事で解説したジェネリックのサポートも、合わせてご覧いただければと思います。