2012-04-30 11 views
4

ルート登録とControllerFactoryを確認するユニットテストを作成して、特定のURL、特定のコントローラが作成されるようにします。このような何か:Test ControllerFactory(開始前の初期化段階)

Assert.UrlMapsToController("~/Home/Index",typeof(HomeController)); 

私は著書「プロASP.NET MVC 3の枠組み」から取られたコードを変更した、そしてControllerFactory.CreateController()呼び出しはInvalidOperationExceptionがスローにすることを除いて、それは完璧になるようで、 This method cannot be called during the application's pre-start initialization stage.

私はMVCソースコードをダウンロードし、問題の原因を探してデバッグしました。これはControllerFactoryから参照されるすべてのアセンブリを検索し、潜在的なコントローラを見つけることができます。どこかCreateControllerコールスタックで、具体的なトラブルメーカーの呼び出しはこれです:

internal sealed class BuildManagerWrapper : IBuildManager { 
    //... 

    ICollection IBuildManager.GetReferencedAssemblies() { 
     // This bails with InvalidOperationException with the message 
     // "This method cannot be called during the application's pre-start 
     // initialization stage." 
     return BuildManager.GetReferencedAssemblies(); 
    } 

    //... 
} 

I found a SO commentary on this。私はまだ上記のコードを幸せにするために手動で初期化できるものがあるのだろうかと思います。誰でも?

しかし、それがなければ...私はIBuildManagerの実装から呼び出されることに気付くことができません。私はthe possibility of injecting my own IBuildManagerを探検し、私は次のような問題に遭遇した:

  • IBuildManagerがinternalをマークされているので、私はそれからいくつかの他の許可の導出を必要としています。アセンブリSystem.Web.Mvc.Testには、テストシナリオ用に設計されたMockBuildManagerというクラスがあります。これは完璧です!これは第2の問題を引き起こす。
  • 配布可能なMVCは、近くではSystem.Web.Mvc.Testアセンブリ(DOH!)に付属していません。
  • MVC配布可能ファイルにSystem.Web.Mvc.Testアセンブリが付属していても、MockBuildManagerのインスタンスを持つのはソリューションの半分に過ぎません。そのインスタンスをDefaultControllerFactoryに入力する必要もあります。残念ながら、これを達成するためのプロパティ設定ツールにはinternal(DOH!)とマークされています。要するに

私はMVCフレームワークを「初期化」する別の方法を見つけない限り、私のオプションは今のいずれかにある:

  • 私ができるように、完全DefaultControllerFactoryとその依存関係のためのソースコードを複製元のGetReferencedAssemblies()問題をバイパスしてください。
  • MVCソースコードに基づいて、配布可能なMVCをMVCの自分のビルドに完全に置き換えてください。ほんの数のカップルを使って、internal修飾子を削除してください。 (!ぐふダブル)

ところで、私はMvcContrib「TestHelperは」私の目標を達成するための外観を持っていることを知っているが、私は単にコントローラを見つけるためにリフレクションを使用していると思わ - というより取得するために、実際のIControllerFactoryを使用してコントローラータイプ/インスタンス。

このテスト機能が必要な大きな理由は、DefaultControllerFactoryに基づいて、検証したいカスタムコントローラファクトリを作成したことです。

答えて

1

私はここで何を達成しようとしているのかよく分かりません。あなたのルート設定をテストするだけの場合。内部をハックするのではなく、それをテストする方が良い方法です。TDDの第1のルール:あなたが書いたコード(この場合はルーティング設定であり、実際のルート解決テクニックはMVCではありません)だけをテストします。

ルート設定をテストするためのポスト/ブログがたくさんあります( 'mvcテストルート'についてはgoogle)。 httpContextでリクエストを嘲笑してGetRouteDataを呼び出すということになります。

buildmanagerを模擬するために実際に忍者のスキルが必要な場合:私は(LinqPad)実験的テストに使用する内部インターフェイスの方法があります。ほとんどの.netアセンブリには、現在、InternalsVisibleToAttributeが設定されています。おそらく、別の署名付きテストアセンブリを指している可能性があります。この属性のターゲットアセンブリをスキャンし、その名前(および公開キートークン)と一致するアセンブリをオンザフライで作成することで、内部に簡単にアクセスできます。

私は個人的にこの技術を実働テストコードで使用しないことに心から感謝します。いくつかの複雑なアイデアを分離するのは良い方法です。

void Main() 
{ 
    var bm = BuildManagerMockBase.CreateMock<MyBuildManager>(); 
    bm.FileExists("IsCool?").Dump(); 
} 

public class MyBuildManager : BuildManagerMockBase 
{ 
    public override bool FileExists(string virtualPath) { return true; } 
} 

public abstract class BuildManagerMockBase 
{ 
    public static T CreateMock<T>() 
     where T : BuildManagerMockBase 
    { 
     // Locate the mvc assembly 
     Assembly mvcAssembly = Assembly.GetAssembly(typeof(Controller)); 

     // Get the type of the buildmanager interface 
     var buildManagerInterface = mvcAssembly.GetType("System.Web.Mvc.IBuildManager",true); 

     // Locate the "internals visible to" attribute and create a public key token that matches the one specified. 
     var internalsVisisbleTo = mvcAssembly.GetCustomAttributes(typeof (InternalsVisibleToAttribute), true).FirstOrDefault() as InternalsVisibleToAttribute; 
     var publicKeyString = internalsVisisbleTo.AssemblyName.Split("=".ToCharArray())[1]; 
     var publicKey = ToBytes(publicKeyString); 

     // Create a fake System.Web.Mvc.Test assembly with the public key token set 
     AssemblyName assemblyName = new AssemblyName(); 
     assemblyName.Name = "System.Web.Mvc.Test"; 
     assemblyName.SetPublicKey(publicKey); 

     // Get the domain of our current thread to host the new fake assembly 
     var domain = Thread.GetDomain(); 
     var assemblyBuilder = domain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); 
     moduleBuilder = assemblyBuilder.DefineDynamicModule("System.Web.Mvc.Test", "System.Web.Mvc.Test.dll"); 
     AppDomain currentDom = domain; 
     currentDom.TypeResolve += ResolveEvent; 

     // Create a new type that inherits from the provided generic and implements the IBuildManager interface 
     var typeBuilder = moduleBuilder.DefineType("Cheat", TypeAttributes.NotPublic | TypeAttributes.Class, typeof(T), new Type[] { buildManagerInterface });  
     Type cheatType = typeBuilder.CreateType(); 

     // Magic! 
     var ret = Activator.CreateInstance(cheatType) as T; 

     return ret; 
    } 

    private static byte[] ToBytes(string str) 
    { 
     List<Byte> bytes = new List<Byte>(); 

     while(str.Length > 0) 
     { 
      var bstr = str.Substring(0, 2); 
      bytes.Add(Convert.ToByte(bstr, 16)); 
      str = str.Substring(2); 
     } 

     return bytes.ToArray(); 
    } 

    private static ModuleBuilder moduleBuilder; 

    private static Assembly ResolveEvent(Object sender, ResolveEventArgs args) 
    { 
     return moduleBuilder.Assembly; 
    } 

    public virtual bool FileExists(string virtualPath)  { throw new NotImplementedException(); } 
    public virtual Type GetCompiledType(string virtualPath) { throw new NotImplementedException(); } 
    public virtual ICollection GetReferencedAssemblies() { throw new NotImplementedException(); } 
    public virtual Stream ReadCachedFile(string fileName) { throw new NotImplementedException(); } 
    public virtual Stream CreateCachedFile(string fileName) { throw new NotImplementedException(); } 
} 
+1

IOCコンテナからコントローラをプルするカスタムコントローラファクトリをテストしようとして、同じ問題が発生しています。だから私の使用例は、コントローラがGetControllerInstance()メソッドのIOCコンテナから正しいコントローラを引っ張っていることを確認することです(プロテクトされているため直接テストできず、明らかにCreateController()を通してしかアクセスできません)。しかし、例外がスローされるため、コードはそれほど遠くには達しません。 – Thierry

関連する問題