2016-07-28 39 views
1

好的,我知道這聽起來像一個奇怪的請求,但這是我的問題。我想爲我的WCF服務編寫一些集成測試;我有幾個關鍵路徑可以確保我們的行爲正確。一個測試是確保在關鍵位置拋出正確的異常,並且正確傳播管道而不會在錯誤的地方截獲。在Simple Injector中刪除註冊的裝飾器

所以要做到這一點,我重寫了一個模擬對象的現有註冊,它會拋出我想要在我想要拋出的位置處測試的異常。這部分工作正常。

接下來,我想解析我的命令處理程序(被測系統),調用句柄方法並聲明發生了正確的異常。

問題是,當我解析我的命令處理程序時,我實際上通過底部的命令處理程序返回一個loooong裝飾器鏈。在這個鏈的最頂端是一個裝飾器,它是我的全局異常處理器。這是這個頂端的異常處理裝飾器,我需要取消註冊,因爲它阻止我能夠斷言該異常被拋出。我的容器引導程序非常複雜,所以我絕對不希望在我的測試項目中重新創建它的副本,並減去這一個裝飾器。

如果它只是一個標準註冊,我可以簡單地重寫註冊與模仿異常處理程序,重新拋出異常。據我所知,儘管似乎無法重寫裝飾器的註冊。無論如何,我寧願不去那條路。它只是使測試複雜化了一個額外的模擬。如果我可以取消註冊裝飾者會更好。

如果無法取消註冊裝飾者,那麼我的下一個最佳解決方案是什麼?將選項標誌添加到我的引導程序以啓用/禁用某些註冊?

謝謝。

回答

3

在Simple Injector中無法取消註冊。你可以replace an existing registration,但是這個方法在處理裝飾器時不起作用。裝飾者通過在ExpressionBuilt事件中添加一個代表添加到Simple Injector內部。由於註冊代理無處存儲,目前在技術上不可能「註銷」裝飾註冊。

解決方法是簡單地而不是註冊裝飾器。這聽起來可能很愚蠢,但這是我一直在使用的一種做法,即使是使用其他容器。

你能做些什麼,例如,將註冊的公共部分提取到一個單獨的方法,我們稱之爲BuildUp。此方法缺少與使用它的不同應用程序不同的註冊。在你的情況下,你至少有2個「應用程序」;真正的應用程序和集成測試項目。這兩個項目都可以撥打BuildUp,並在致電BuildUp之前或之後添加額外的註冊。例如:

var container = new Container(); 
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle(); 

container.RegisterDecorator(typeof(ICommandHandler<>), 
    typeof(InnerMostCommandHandlerDecorator<>)); 

CompositionRoot.BuildUp(container); 

container.RegisterDecorator(typeof(ICommandHandler<>), 
    typeof(OuterMostCommandHandlerDecorator<>)); 

這種方法似乎在你的情況很好地工作,因爲你要添加一個「最外面的」裝飾。此外,讓BuildUp在您的註冊中留下「漏洞」,通常會很容易看到某些應用程序忘記填寫空白時的情況,因爲您可以通過調用container.Verify()來讓Simple Injector快速失敗。

另一種常用的方式將配置對象傳遞給BuildUp方法。該配置對象可以包含必要的信息,以便根據調用者的要求進行正確的註冊。舉例來說,這樣的配置對象可以有一個簡單的布爾標誌:

public static void Build(Container container, ApplicationConfig config) { 

    // Lot's of registrations 

    if (config.AddGlobalExceptionHandler) { 
     container.RegisterDecorator(typeof(ICommandHandler<>), 
      typeof(GlobalExceptionHandlerCommandHandlerDecorator<>)); 
    } 
} 

的配置對象傳遞到BuildUp方法也是解耦從配置系統BuildUp方法的好方法。這使您可以在集成測試期間更輕鬆地撥打BuildUp,而不必在測試項目中擁有完整配置文件的副本。

除了使用標誌屬性外,還可以在配置對象中包含裝飾器的完整列表,並讓BuildUp方法遍歷它並註冊它們。這使得他們在註冊前呼叫者從列表中刪除或添加裝飾:

var config = new ApplicationConfig(); 

// Remove decorator 
config.CommandHandlerDecorators.Remove(
    typeof(AuthorizationCommandHandlerDecorator<>)); 

// Add decorator after another decorator 
config.CommandHandlerDecorators.Insert(
    index: 1 + config.CommandHandlerDecorators.IndexOf(
     typeof(TransactionCommandHandlerDecorator<>)), 
    item: typeof(DeadlockRetryCommandHandlerDecorator<>)); 

// Add an outer most decorator 
config.CommandHandlerDecorators.Add(
    typeof(TestPerformanceProfilingCommandHandlerDecorator<>)); 

CompositionRoot.BuildUp(container, config); 


public static void BuildUp(Container container, ApplicationConfig config) { 

    // Lot's of registrations here. 

    config.CommandHandlerDecorators.ForEach(type => 
     container.RegisterDecorator(typeof(ICommandHandler<>), type)); 
} 

我已經在過去使用所有這三種方法非常成功。選擇哪個選項取決於您的需求。

3

據我所知,不可能刪除任何註冊。

單元測試通常不會使用容器。自從您執行集成測試以來,使用容器確實是必須的。

我可以想出兩種做你想做的方式。

第一種方法是將一些選項標誌傳遞給在生產環境和測試環境之間交換的引導程序。

第二個是考慮你的測試方法。從你的問題看來,你的ICommandHandler鏈中的某個部分應該拋出一個異常。

我認爲這是非常簡單的測試使用正常的單元測試,而不是一個集成測試。在這種情況下,您不會使用容器,而是手動創建鏈條。

一種你單元測試commandhandler會是這麼簡單:

[TestMethod] 
[ExpectedException(typeof(InvalidOperationException))] 
public void CommandHandlerThrowsCorrectException() 
{ 
    var handler = new Decorator1(new Decorator2(new MyHandler())); 

    handler.Handle(new MyCommand); 
} 

您可以使用其他集成測試,以檢查是否命令交給建築WCF服務結果和處理正確ICommandHandler鏈。

我通常使用下面的測試設置:

  • 使用單元測試用於測試你手頭所有的情況下,在不使用容器=>這意味着在應用程序中每個組件的至少一個單元測試
  • 使用單元測試來測試每個單獨的裝飾器,而不使用容器=>GenericExceptionCommandHandlerDecorator在你的情況下,處理異常
  • 使用單元測試來測試對容器所做的註冊是否正確以及是否應用裝飾器按照正確的順序,使用conta核能研究所。部分工作已由容器完成,如果使用內置的verification of the made registrations,則使用container.Verify()
  • 儘可能少地使用集成測試,只是爲了測試應用程序的工作原理和流程是否應該如此。由於每個組件和裝飾器都經過了單元測試,所以在集成測試中測試應用程序的行爲的需求要少得多。總是會有場景模擬用戶與應用程序的交互,但應該很少見,大部分都是由單元測試覆蓋。
相關問題