Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In diesem Thema werden einige spezifische Techniken für Komponententestcontroller in Web API 2 beschrieben. Bevor Sie dieses Thema lesen, sollten Sie das Lernprogramm "Komponententests" ASP.NET Web-API 2 lesen, das zeigt, wie Sie Ihrer Lösung ein Komponententestprojekt hinzufügen.
Im Lernprogramm verwendete Softwareversionen
- Visual Studio 2017
- Web-API 2
- Moq 4.5.30
Hinweis
Ich habe Moq verwendet, aber die gleiche Idee gilt für jedes simulierte Framework. Moq 4.5.30 (und höher) unterstützt Visual Studio 2017, Roslyn und .NET 4.5 und höhere Versionen.
Ein gängiges Muster bei Komponententests ist "arrange-act-assert":
- Anordnen: Richten Sie alle Voraussetzungen ein, damit der Test ausgeführt werden kann.
- Act: Führen Sie den Test aus.
- Assert: Überprüfen Sie, ob der Test erfolgreich war.
Im Anordnungsschritt verwenden Sie häufig simulierte oder Stubobjekte. Dadurch wird die Anzahl der Abhängigkeiten minimiert, sodass sich der Test auf das Testen einer Sache konzentriert.
Hier sind einige Dinge, die Sie in Ihren Web-API-Controllern komponententesten sollten:
- Die Aktion gibt den richtigen Antworttyp zurück.
- Ungültige Parameter geben die richtige Fehlerantwort zurück.
- Die Aktion ruft die richtige Methode auf der Repository- oder Dienstebene auf.
- Wenn die Antwort ein Domänenmodell enthält, überprüfen Sie den Modelltyp.
Dies sind einige der allgemeinen Dinge, die Sie testen müssen, aber die Besonderheiten hängen von der Controllerimplementierung ab. Insbesondere macht es einen großen Unterschied, ob Ihre Controlleraktionen HttpResponseMessage oder IHttpActionResult zurückgeben. Weitere Informationen zu diesen Ergebnistypen finden Sie unter "Aktionsergebnisse" in Der Web-API 2.
Testen von Aktionen, die HttpResponseMessage zurückgeben
Nachfolgend sehen Sie ein Beispiel für einen Controller, dessen Aktionen HttpResponseMessage zurückgeben.
public class ProductsController : ApiController
{
IProductRepository _repository;
public ProductsController(IProductRepository repository)
{
_repository = repository;
}
public HttpResponseMessage Get(int id)
{
Product product = _repository.GetById(id);
if (product == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
return Request.CreateResponse(product);
}
public HttpResponseMessage Post(Product product)
{
_repository.Add(product);
var response = Request.CreateResponse(HttpStatusCode.Created, product);
string uri = Url.Link("DefaultApi", new { id = product.Id });
response.Headers.Location = new Uri(uri);
return response;
}
}
Beachten Sie, dass der Controller eine "Abhängigkeitsinjektion" verwendet, um ein IProductRepository zu injizieren. Dies macht den Controller testfähiger, da Sie ein pseudorepository einfügen können. Der folgende Komponententest überprüft, ob die Get Methode einen Product in den Antworttext schreibt. Nehmen Sie an, dass repository ein Mock IProductRepository ist.
[TestMethod]
public void GetReturnsProduct()
{
// Arrange
var controller = new ProductsController(repository);
controller.Request = new HttpRequestMessage();
controller.Configuration = new HttpConfiguration();
// Act
var response = controller.Get(10);
// Assert
Product product;
Assert.IsTrue(response.TryGetContentValue<Product>(out product));
Assert.AreEqual(10, product.Id);
}
Es ist wichtig, die Anforderung und Konfiguration auf dem Controller festzulegen. Andernfalls schlägt der Test mit einer ArgumentNullException oder InvalidOperationException fehl.
Testen der Verknüpfungsgenerierung
Die Post Methode ruft UrlHelper.Link auf, um Verknüpfungen in der Antwort zu erstellen. Dies erfordert etwas mehr Setup im Komponententest:
[TestMethod]
public void PostSetsLocationHeader()
{
// Arrange
ProductsController controller = new ProductsController(repository);
controller.Request = new HttpRequestMessage {
RequestUri = new Uri("http://localhost/api/products")
};
controller.Configuration = new HttpConfiguration();
controller.Configuration.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
controller.RequestContext.RouteData = new HttpRouteData(
route: new HttpRoute(),
values: new HttpRouteValueDictionary { { "controller", "products" } });
// Act
Product product = new Product() { Id = 42, Name = "Product1" };
var response = controller.Post(product);
// Assert
Assert.AreEqual("http://localhost/api/products/42", response.Headers.Location.AbsoluteUri);
}
Die UrlHelper-Klasse benötigt die Anforderungs-URL und Routendaten, sodass der Test Werte für diese festlegen muss. Eine andere Option ist UrlHelper zu simulieren oder als Stub zu verwenden. Bei diesem Ansatz ersetzen Sie den Standardwert von "ApiController.Url" durch eine simulierte oder stub-Version, die einen festen Wert zurückgibt.
Lassen Sie uns den Test mithilfe des Moq-Frameworks neu schreiben. Installieren Sie das Moq NuGet-Paket im Testprojekt.
[TestMethod]
public void PostSetsLocationHeader_MockVersion()
{
// This version uses a mock UrlHelper.
// Arrange
ProductsController controller = new ProductsController(repository);
controller.Request = new HttpRequestMessage();
controller.Configuration = new HttpConfiguration();
string locationUrl = "http://location/";
// Create the mock and set up the Link method, which is used to create the Location header.
// The mock version returns a fixed string.
var mockUrlHelper = new Mock<UrlHelper>();
mockUrlHelper.Setup(x => x.Link(It.IsAny<string>(), It.IsAny<object>())).Returns(locationUrl);
controller.Url = mockUrlHelper.Object;
// Act
Product product = new Product() { Id = 42 };
var response = controller.Post(product);
// Assert
Assert.AreEqual(locationUrl, response.Headers.Location.AbsoluteUri);
}
In dieser Version müssen Sie keine Routendaten einrichten, da die simulierte UrlHelper eine Konstante Zeichenfolge zurückgibt.
Testen von Aktionen, die IHttpActionResult zurückgeben
In Web-API 2 kann eine Controlleraktion IHttpActionResult zurückgeben, die mit ActionResult in ASP.NET MVC vergleichbar ist. Die IHttpActionResult-Schnittstelle definiert ein Befehlsmuster zum Erstellen von HTTP-Antworten. Anstatt die Antwort direkt zu erstellen, gibt der Controller ein IHttpActionResult zurück. Später ruft die Pipeline das IHttpActionResult auf, um die Antwort zu erstellen. Dieser Ansatz erleichtert das Schreiben von Komponententests, da Sie viele setups überspringen können, die für HttpResponseMessage erforderlich sind.
Hier ist ein Beispielcontroller, dessen Aktionen IHttpActionResult zurückgeben.
public class Products2Controller : ApiController
{
IProductRepository _repository;
public Products2Controller(IProductRepository repository)
{
_repository = repository;
}
public IHttpActionResult Get(int id)
{
Product product = _repository.GetById(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
public IHttpActionResult Post(Product product)
{
_repository.Add(product);
return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);
}
public IHttpActionResult Delete(int id)
{
_repository.Delete(id);
return Ok();
}
public IHttpActionResult Put(Product product)
{
// Do some work (not shown).
return Content(HttpStatusCode.Accepted, product);
}
}
Dieses Beispiel zeigt einige gängige Muster mit IHttpActionResult. Sehen wir uns an, wie Sie die Komponenten testen.
Aktion gibt 200 (OK) mit einem Antworttext zurück.
Die Get Methode ruft auf Ok(product) , wenn das Produkt gefunden wird. Stellen Sie im Komponententest sicher, dass der Rückgabetyp "OkNegotiatedContentResult " lautet und das zurückgegebene Produkt über die richtige ID verfügt.
[TestMethod]
public void GetReturnsProductWithSameId()
{
// Arrange
var mockRepository = new Mock<IProductRepository>();
mockRepository.Setup(x => x.GetById(42))
.Returns(new Product { Id = 42 });
var controller = new Products2Controller(mockRepository.Object);
// Act
IHttpActionResult actionResult = controller.Get(42);
var contentResult = actionResult as OkNegotiatedContentResult<Product>;
// Assert
Assert.IsNotNull(contentResult);
Assert.IsNotNull(contentResult.Content);
Assert.AreEqual(42, contentResult.Content.Id);
}
Beachten Sie, dass der Komponententest das Aktionsergebnis nicht ausführt. Sie können davon ausgehen, dass das Aktionsergebnis die HTTP-Antwort richtig erstellt. (Deshalb verfügt das Web-API-Framework über eigene Komponententests!)
Aktion gibt 404 (Nicht gefunden) zurück.
Die Get Methode ruft auf NotFound() , wenn das Produkt nicht gefunden wird. In diesem Fall überprüft der Komponententest nur, ob der Rückgabetyp NotFoundResult ist.
[TestMethod]
public void GetReturnsNotFound()
{
// Arrange
var mockRepository = new Mock<IProductRepository>();
var controller = new Products2Controller(mockRepository.Object);
// Act
IHttpActionResult actionResult = controller.Get(10);
// Assert
Assert.IsInstanceOfType(actionResult, typeof(NotFoundResult));
}
Aktion gibt 200 (OK) ohne Antworttext zurück.
Die Delete Methode ruft Ok() auf, um eine leere HTTP 200-Antwort zurückzugeben. Wie im vorherigen Beispiel überprüft der Komponententest den Rückgabetyp in diesem Fall OkResult.
[TestMethod]
public void DeleteReturnsOk()
{
// Arrange
var mockRepository = new Mock<IProductRepository>();
var controller = new Products2Controller(mockRepository.Object);
// Act
IHttpActionResult actionResult = controller.Delete(10);
// Assert
Assert.IsInstanceOfType(actionResult, typeof(OkResult));
}
Aktion gibt 201 (Created) mit einem Location-Header zurück
Die Post Methode ruft CreatedAtRoute auf, um eine HTTP 201-Antwort mit einem URI im Location-Header zurückzugeben. Überprüfen Sie im Komponententest, ob die Aktion die richtigen Routingwerte festlegt.
[TestMethod]
public void PostMethodSetsLocationHeader()
{
// Arrange
var mockRepository = new Mock<IProductRepository>();
var controller = new Products2Controller(mockRepository.Object);
// Act
IHttpActionResult actionResult = controller.Post(new Product { Id = 10, Name = "Product1" });
var createdResult = actionResult as CreatedAtRouteNegotiatedContentResult<Product>;
// Assert
Assert.IsNotNull(createdResult);
Assert.AreEqual("DefaultApi", createdResult.RouteName);
Assert.AreEqual(10, createdResult.RouteValues["id"]);
}
Aktion gibt einen weiteren 2xx mit einem Antwortkörper zurück.
Die Put Methode ruft Content auf, um eine HTTP 202 (Accepted)-Antwort mit einem Antworttext zurückzugeben. Dieser Fall ähnelt der Rückgabe von 200 (OK), aber der Komponententest sollte auch den Statuscode überprüfen.
[TestMethod]
public void PutReturnsContentResult()
{
// Arrange
var mockRepository = new Mock<IProductRepository>();
var controller = new Products2Controller(mockRepository.Object);
// Act
IHttpActionResult actionResult = controller.Put(new Product { Id = 10, Name = "Product" });
var contentResult = actionResult as NegotiatedContentResult<Product>;
// Assert
Assert.IsNotNull(contentResult);
Assert.AreEqual(HttpStatusCode.Accepted, contentResult.StatusCode);
Assert.IsNotNull(contentResult.Content);
Assert.AreEqual(10, contentResult.Content.Id);
}