2012-08-02 114 views
10

我有以下控制器動作測試的WebAPI控制器Url.Link

public void Post(Dto model) 
{ 
    using (var message = new MailMessage()) 
    { 
     var link = Url.Link("ConfirmAccount", new { model.Id }); 

     message.To.Add(model.ToAddress); 
     message.IsBodyHtml = true; 
     message.Body = string.Format(@"<p>Click <a href=""{0}"">here</a> to complete your registration.<p><p>You may also copy and paste this link into your browser.</p><p>{0}</p>", link); 

     MailClient.Send(message); 
    } 
} 

爲了驗證這一點,我需要設置控制器上下文

var httpConfiguration = new HttpConfiguration(new HttpRouteCollection { { "ConfirmAccount", new HttpRoute() } }); 
var httpRouteData = new HttpRouteData(httpConfiguration.Routes.First()); 
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "http://localhost"); 
sut = new TheController 
{ 
    ControllerContext = new HttpControllerContext(httpConfiguration, httpRouteData, httpRequestMessage), 
    MailClient = new SmtpClient { PickupDirectoryLocation = location } 
}; 

這似乎是一個很大的設置來測試創建的鏈接。有沒有更乾淨的方法來做到這一點?我已經閱讀了關於內存中的服務器,但看起來它比httpclient更適用於直接測試控制器。

+0

+1我認爲REST服務的重點在於允許可鏈接的資源。我對WebAPI Link/Url實用程序非常不滿。引用路由名稱似乎很脆弱,測試故事同樣痛苦。希望有一些改進... – 2013-04-10 16:40:09

回答

2

我遇到了同樣的白癡。我能找到的所有參考資料都希望你能夠模擬請求/控制器,這是(正如你所指出的)很多工作。

具體參考:

我還沒有到處嘗試實際的Mocking框架,所以我有一個幫助器類來「構建」我的控制器。因此,而不是

sut = new TheController { ... } 

我使用類似:

// actually rolled together to `sut = MyTestSetup.GetController(method, url)` 
sut = new TheController()... 
MyTestSetup.FakeRequest(sut, HttpMethod.Whatever, "~/the/expected/url"); 

供參考,該方法基本上是:

public void FakeRequest(ApiController controller, HttpMethod method = null, string requestUrl = null, string controllerName = null) { 
    HttpConfiguration config = new HttpConfiguration(); 
    // rebuild the expected request 
    var request = new HttpRequestMessage(null == method ? this.requestMethod : method, string.IsNullOrWhiteSpace(requestUrl) ? this.requestUrl : requestUrl); 
    //var route = System.Web.Routing.RouteTable.Routes["DefaultApi"]; 
    var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}"); 

    // TODO: get from application? maybe like https://stackoverflow.com/a/5943810/1037948 
    var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", string.IsNullOrWhiteSpace(controllerName) ? this.requestController : controllerName } }); 

    controller.ControllerContext = new HttpControllerContext(config, routeData, request); 

    // attach fake request 
    controller.Request = request; 
    controller.Request.Properties[/* "MS_HttpConfiguration" */ HttpPropertyKeys.HttpConfigurationKey] = config; 
} 
+0

我現在使用[RazorGenerator](http://razorgenerator.codeplex.com/)來構建文本,而不是手動構建控制器中的鏈接。 – 2012-08-21 00:39:05

12

下面是測試UrlHelper所需的絕對最少的代碼沒有任何形式嘲笑圖書館。扔我的東西(並花了我一些時間來追查)是,你需要設置請求的IHttpRouteData。如果您不是IHttpRoute實例將無法生成導致空的URL的虛擬路徑。

public class FooController : ApiController 
    { 
     public string Get() 
     { 
      return Url.Link(RouteNames.DefaultRoute, new { controller = "foo", id = "10" }); 
     } 
    } 

    [TestFixture] 
    public class FooControllerTests 
    { 
     FooController controller; 

     [SetUp] 
     public void SetUp() 
     { 
      var config = new HttpConfiguration(); 

      config.Routes.MapHttpRoute(
       name: "Default", 
       routeTemplate: "api/{controller}/{id}", 
       defaults: new { id = RouteParameter.Optional }); 

      var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost"); 
      request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config; 
      request.Properties[HttpPropertyKeys.HttpRouteDataKey] = new HttpRouteData(new HttpRoute()); 

      controller = new FooController 
      { 
       Request = request 
      }; 
     } 

     [Test] 
     public void Get_returns_link() 
     { 
      Assert.That(controller.Get(), Is.EqualTo("http://localhost/api/foo/10")); 
     } 
    } 
+6

自從web api2問世以來,有什麼更新? – GetFuzzy 2013-12-14 04:53:49

+0

我還必須創建'FooController'的'ControllerContext'屬性,包括其'ControllerDescriptor'。我繼承了這段代碼,並不確定我使用的是哪一個web api,但它是MVC 4. – 2014-08-28 17:46:17

+0

@GetFuzzy有點晚了,但我剛剛得出了這個結論,並且正在添加一個答案。 – krillgar 2015-09-16 14:03:57

11

我開始在Web API 2.0中使用這種方法。

如果您正在使用模擬庫(並且您真的應該進行任何真實世界的單元測試),您可以直接模擬UrlHelper對象,因爲它的所有方法都是virtual

var mock = new Mock<UrlHelper>(); 
mock.Setup(m => m.Link(It.IsAny<string>(), It.IsAny<object>())).Returns("test url"); 

var controller = new FooController { 
    Url = mock.Object 
}; 

這比本·福斯特的回答遠清晰的解決方案,因爲這種做法,你需要路由添加到配置爲您所使用的每名。這可能很容易改變或成爲一個荒謬的大量建立路線。

+0

應該是被接受的答案。簡單而優雅。我也需要這個代碼。 var context = new HttpRequestContext {Url = mockUrlHelper.Object}; request.Properties.Add(HttpPropertyKeys.RequestContextKey,context); – CountZero 2016-12-03 23:04:49

+0

@CountZero是什麼導致你需要這個?如果你想補充說,作爲你自己的答案,並建立我的,這可能是好的,或者我們可以在這裏結合起來。由你決定,但是一些背景(不是雙關語)會有所幫助。 – krillgar 2016-12-04 20:52:07