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.
Von Steve Smith
Unit-Tests beinhalten das Testen einer App-Komponente isoliert von ihrer Infrastruktur und ihren Abhängigkeiten. Beim Unit-Testen der Controllerlogik werden nur die Inhalte einer einzelnen Aktion getestet, nicht das Verhalten ihrer Abhängigkeiten oder des Frameworks selbst.
Unit-Tests für Controller
Richten Sie Komponententests für Controlleraktionen ein, um sich auf das Verhalten des Controllers zu konzentrieren. Bei einem Unit-Test eines Controllers werden Szenarien wie Filter, Routing und Modellbindung vermieden. Tests, die die Interaktionen zwischen Komponenten betreffen, die gemeinsam auf eine Anforderung reagieren, werden mithilfe von Integrationstests behandelt. Weitere Informationen zu Integrationstests finden Sie unter Integrationstests in ASP.NET Core.
Wenn Sie benutzerdefinierte Filter und Routen schreiben, sollten Sie diese isoliert mit einem Unit-Test testen, nicht als Teil von Tests für eine bestimmte Controlleraktion.
Zur Veranschaulichung von Unit-Tests für Controller sehen Sie sich den folgenden Controller in der Beispiel-App an.
Beispielcode anzeigen oder herunterladen (Vorgehensweise zum Herunterladen)
Der Home-Controller zeigt eine Liste von Brainstormingsitzungen an und ermöglicht das Erstellen neuer Brainstormingsitzungen mit einer POST-Anforderung:
public class HomeController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;
public HomeController(IBrainstormSessionRepository sessionRepository)
{
_sessionRepository = sessionRepository;
}
public async Task<IActionResult> Index()
{
var sessionList = await _sessionRepository.ListAsync();
var model = sessionList.Select(session => new StormSessionViewModel()
{
Id = session.Id,
DateCreated = session.DateCreated,
Name = session.Name,
IdeaCount = session.Ideas.Count
});
return View(model);
}
public class NewSessionModel
{
[Required]
public string SessionName { get; set; }
}
[HttpPost]
public async Task<IActionResult> Index(NewSessionModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
else
{
await _sessionRepository.AddAsync(new BrainstormSession()
{
DateCreated = DateTimeOffset.Now,
Name = model.SessionName
});
}
return RedirectToAction(actionName: nameof(Index));
}
}
Der vorangehende Controller:
- Entspricht dem Prinzip der expliziten Abhängigkeiten.
- Er erwartet, dass Abhängigkeitsinjektion (Dependency Injection, DI) eine Instanz von
IBrainstormSessionRepositorybereitstellt. - Er kann mit einem simulierten
IBrainstormSessionRepository-Dienst mithilfe eines Pseudoobjektframeworks wie Moq getestet werden. Ein Mock-Objekt ist ein nachgebildetes Objekt mit einem vordefinierten Satz von Verhaltensweisen für Eigenschaften und Methoden, das zum Testen verwendet wird. Weitere Informationen finden Sie unter Einführung in Integrationstests.
Die HTTP GET Index-Methode enthält weder Schleifen noch Verzweigungen und ruft nur eine Methode auf. Der Komponententest für diese Aktion:
- Simuliert den
IBrainstormSessionRepository-Dienst mit derGetTestSessions-Methode.GetTestSessionserstellt zwei simulierte Brainstormingsitzungen mit Datumsangaben und Sitzungsnamen. - Führt die
Index-Methode aus. - Nimmt Assertionen für das von der Methode zurückgegebene Ergebnis vor:
- Ein ViewResult wird zurückgegeben.
- Das ViewDataDictionary.Model ist ein
StormSessionViewModel. - Es sind zwei Brainstorming-Sitzungen in
ViewDataDictionary.Modelgespeichert.
[Fact]
public async Task Index_ReturnsAViewResult_WithAListOfBrainstormSessions()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync())
.ReturnsAsync(GetTestSessions());
var controller = new HomeController(mockRepo.Object);
// Act
var result = await controller.Index();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<StormSessionViewModel>>(
viewResult.ViewData.Model);
Assert.Equal(2, model.Count());
}
private List<BrainstormSession> GetTestSessions()
{
var sessions = new List<BrainstormSession>();
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 2),
Id = 1,
Name = "Test One"
});
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 1),
Id = 2,
Name = "Test Two"
});
return sessions;
}
Die HTTP POST Index-Methode des Home-Controllers überprüft Folgendes:
- Wenn ModelState.IsValid den Wert
falsehat, gibt die Aktionsmethode ein 400 Bad RequestViewResult mit den entsprechenden Daten zurück. - Wenn
ModelState.IsValidtrueist:- Die
Add-Methode des Repositorys wird aufgerufen. - Ein RedirectToActionResult wird mit den richtigen Argumenten zurückgegeben.
- Die
Ein ungültiger Modellzustand wird getestet, indem mit AddModelError Fehler hinzugefügt werden, wie im ersten Test unten gezeigt:
[Fact]
public async Task IndexPost_ReturnsBadRequestResult_WhenModelStateIsInvalid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync())
.ReturnsAsync(GetTestSessions());
var controller = new HomeController(mockRepo.Object);
controller.ModelState.AddModelError("SessionName", "Required");
var newSession = new HomeController.NewSessionModel();
// Act
var result = await controller.Index(newSession);
// Assert
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
Assert.IsType<SerializableError>(badRequestResult.Value);
}
[Fact]
public async Task IndexPost_ReturnsARedirectAndAddsSession_WhenModelStateIsValid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.AddAsync(It.IsAny<BrainstormSession>()))
.Returns(Task.CompletedTask)
.Verifiable();
var controller = new HomeController(mockRepo.Object);
var newSession = new HomeController.NewSessionModel()
{
SessionName = "Test Name"
};
// Act
var result = await controller.Index(newSession);
// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
mockRepo.Verify();
}
Wenn ModelState nicht gültig ist, wird das gleiche ViewResult wie für eine GET-Anforderung zurückgegeben. Der Test versucht nicht, ein ungültiges Modell zu übergeben. Das Übergeben eines ungültigen Modells ist kein gültiger Ansatz, da die Modellbindung nicht ausgeführt wird (obwohl ein Integrationstest Modellbindung verwendet). In diesem Fall wird die Modellbindung nicht getestet. Diese Unit-Tests testen nur den Code in der Action-Methode.
Der zweite Test bestätigt dies, wenn der ModelState gültig ist:
- Eine neue
BrainstormSessionwird hinzugefügt (über das Repository). - Die Methode gibt ein
RedirectToActionResultmit den erwarteten Eigenschaften zurück.
Simulierte Aufrufe, die nicht aufgerufen werden, werden normalerweise ignoriert. Durch Aufrufen von Verifiable am Ende des Einrichtungsaufrufs wird jedoch eine Pseudoüberprüfung im Test ermöglicht. Dies erfolgt mit dem Aufruf von mockRepo.Verify. Damit schlägt der Test fehl, wenn die erwartete Methode nicht aufgerufen wurde.
Note
Mit der in diesem Beispiel verwendeten Moq-Bibliothek können überprüfbare (oder „strikte“) Pseudoobjekte mit nicht überprüfbaren Pseudoobjekten (auch „Loose-Mocks“ oder „Stubs“ genannt) kombiniert werden. Erfahren Sie mehr über das Anpassen des Mock-Verhaltens mit Moq.
In der Beispiel-App werden Informationen zu einer bestimmten Brainstormingsitzung mit SessionController angezeigt. Der Controller enthält Logik, um ungültige id Werte zu behandeln (es gibt im folgenden Beispiel zwei return-Szenarien, die diese Fälle abdecken). Die endgültige return-Anweisung gibt ein neues StormSessionViewModel an die Ansicht zurück (Controllers/SessionController.cs):
public class SessionController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;
public SessionController(IBrainstormSessionRepository sessionRepository)
{
_sessionRepository = sessionRepository;
}
public async Task<IActionResult> Index(int? id)
{
if (!id.HasValue)
{
return RedirectToAction(actionName: nameof(Index),
controllerName: "Home");
}
var session = await _sessionRepository.GetByIdAsync(id.Value);
if (session == null)
{
return Content("Session not found.");
}
var viewModel = new StormSessionViewModel()
{
DateCreated = session.DateCreated,
Name = session.Name,
Id = session.Id
};
return View(viewModel);
}
}
Die Unit-Tests enthalten einen Test für jedes return-Szenario in der Aktion Index des Session-Controllers:
[Fact]
public async Task IndexReturnsARedirectToIndexHomeWhenIdIsNull()
{
// Arrange
var controller = new SessionController(sessionRepository: null);
// Act
var result = await controller.Index(id: null);
// Assert
var redirectToActionResult =
Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Home", redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
}
[Fact]
public async Task IndexReturnsContentWithSessionNotFoundWhenSessionNotFound()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new SessionController(mockRepo.Object);
// Act
var result = await controller.Index(testSessionId);
// Assert
var contentResult = Assert.IsType<ContentResult>(result);
Assert.Equal("Session not found.", contentResult.Content);
}
[Fact]
public async Task IndexReturnsViewResultWithStormSessionViewModel()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSessions().FirstOrDefault(
s => s.Id == testSessionId));
var controller = new SessionController(mockRepo.Object);
// Act
var result = await controller.Index(testSessionId);
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsType<StormSessionViewModel>(
viewResult.ViewData.Model);
Assert.Equal("Test One", model.Name);
Assert.Equal(2, model.DateCreated.Day);
Assert.Equal(testSessionId, model.Id);
}
Mit dem Wechsel zum Ideas-Controller stellt die App Funktionalität als Web-API auf der api/ideas-Route zur Verfügung:
- Eine Liste von Ideen (
IdeaDTO), die einer Brainstormingsitzung zugeordnet sind, wird von derForSession-Methode zurückgegeben. - Die
Create-Methode fügt einer Sitzung neue Ideen hinzu.
[HttpGet("forsession/{sessionId}")]
public async Task<IActionResult> ForSession(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}
var result = session.Ideas.Select(idea => new IdeaDTO()
{
Id = idea.Id,
Name = idea.Name,
Description = idea.Description,
DateCreated = idea.DateCreated
}).ToList();
return Ok(result);
}
[HttpPost("create")]
public async Task<IActionResult> Create([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var session = await _sessionRepository.GetByIdAsync(model.SessionId);
if (session == null)
{
return NotFound(model.SessionId);
}
var idea = new Idea()
{
DateCreated = DateTimeOffset.Now,
Description = model.Description,
Name = model.Name
};
session.AddIdea(idea);
await _sessionRepository.UpdateAsync(session);
return Ok(session);
}
Vermeiden Sie die Rückgabe von Geschäftsdomänenentitäten direkt über API-Aufrufe. Domänenentitäten:
- Sie enthalten häufig mehr Daten als der Client benötigt.
- Sie koppeln unnötigerweise das interne Domänenmodell der App mit der öffentlich bereitgestellten API.
Die Zuordnung zwischen Domänenentitäten und den Typen, die an den Client zurückgegeben werden, kann ausgeführt werden:
- Manuell mit einer LINQ-
Select, wie sie von der Beispiel-App verwendet wird. Weitere Informationen finden Sie unter LINQ (Language Integrated Query). - Automatisch mit einer Bibliothek, z.B. mit AutoMapper.
Anschließend demonstriert die Beispiel-App Unit-Tests für die API-Methoden Create und ForSession des Ideas-Controllers.
Die Beispiel-App enthält zwei ForSession-Tests. Der erste Test ermittelt, ob ForSession ein NotFoundObjectResult (HTTP Not Found) für eine ungültige Sitzung zurückgibt:
[Fact]
public async Task ForSession_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.ForSession(testSessionId);
// Assert
var notFoundObjectResult = Assert.IsType<NotFoundObjectResult>(result);
Assert.Equal(testSessionId, notFoundObjectResult.Value);
}
Der zweite ForSession-Test ermittelt, ob ForSession eine Liste der Sitzungsideen (<List<IdeaDTO>>) für eine gültige Sitzung zurückgibt. Die Tests untersuchen auch die erste Idee, um zu bestätigen, dass ihre Name-Eigenschaft richtig ist:
[Fact]
public async Task ForSession_ReturnsIdeasForSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSession());
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.ForSession(testSessionId);
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnValue = Assert.IsType<List<IdeaDTO>>(okResult.Value);
var idea = returnValue.FirstOrDefault();
Assert.Equal("One", idea.Name);
}
Um das Verhalten der Create-Methode zu testen, wenn der ModelState ungültig ist, fügt die Beispiel-App dem Controller einen Modellfehler als Teil des Tests hinzu. Versuchen Sie nicht, Modellvalidierung oder Modellbindung in Unit-Tests zu testen – testen Sie einfach das Verhalten der Aktionsmethode, wenn sie mit ungültigem ModelState konfrontiert wird:
[Fact]
public async Task Create_ReturnsBadRequest_GivenInvalidModel()
{
// Arrange & Act
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
controller.ModelState.AddModelError("error", "some error");
// Act
var result = await controller.Create(model: null);
// Assert
Assert.IsType<BadRequestObjectResult>(result);
}
Der zweite Test von Create hängt davon ab, ob das Repository null zurückgibt. Daher wird das Pseudorepository so konfiguriert, dass es null zurückgibt. Es ist nicht nötig, eine Testdatenbank (im Arbeitsspeicher oder anderweitig) zu erstellen und eine Abfrage zu erstellen, um dieses Ergebnis zurückzugeben. Der Test kann mit einer einzigen Anweisung ausgeführt werden, wie der Beispielcode veranschaulicht:
[Fact]
public async Task Create_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.Create(new NewIdeaModel());
// Assert
Assert.IsType<NotFoundObjectResult>(result);
}
Im dritten Create-Test (Create_ReturnsNewlyCreatedIdeaForSession) wird überprüft, ob die UpdateAsync-Methode des Repositorys aufgerufen wird. Der Mock wird mit Verifiable aufgerufen, und die Verify-Methode des gemockten Repositorys wird aufgerufen, um zu bestätigen, dass die als verifizierbar markierte Methode ausgeführt wird. Es ist nicht die Aufgabe des Unit-Tests, sicherzustellen, dass die UpdateAsync-Methode die Daten gespeichert hat – das kann mit einem Integrationstest überprüft werden.
[Fact]
public async Task Create_ReturnsNewlyCreatedIdeaForSession()
{
// Arrange
int testSessionId = 123;
string testName = "test name";
string testDescription = "test description";
var testSession = GetTestSession();
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(testSession);
var controller = new IdeasController(mockRepo.Object);
var newIdea = new NewIdeaModel()
{
Description = testDescription,
Name = testName,
SessionId = testSessionId
};
mockRepo.Setup(repo => repo.UpdateAsync(testSession))
.Returns(Task.CompletedTask)
.Verifiable();
// Act
var result = await controller.Create(newIdea);
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnSession.Ideas.Count());
Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnSession.Ideas.LastOrDefault().Description);
}
Test ActionResult<T>
ActionResult<T> (ActionResult<TValue>) kann einen von ActionResult abgeleiteten Typ oder einen spezifischen Typ zurückgeben.
Die Beispielanwendung enthält eine Methode, die ein List<IdeaDTO> für eine bestimmte Sitzung id zurückgibt. Wenn die Sitzung id nicht vorhanden ist, gibt der Controller NotFound zurück:
[HttpGet("forsessionactionresult/{sessionId}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<ActionResult<List<IdeaDTO>>> ForSessionActionResult(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}
var result = session.Ideas.Select(idea => new IdeaDTO()
{
Id = idea.Id,
Name = idea.Name,
Description = idea.Description,
DateCreated = idea.DateCreated
}).ToList();
return result;
}
Zwei Tests des ForSessionActionResult-Controllers sind in ApiIdeasControllerTests enthalten.
Der erste Test bestätigt, dass der Controller ein ActionResult, aber keine nicht vorhandene Liste von Ideen für eine nicht vorhandene Sitzung id zurückgibt:
- Der
ActionResult-Typ istActionResult<List<IdeaDTO>>. - Result ist ein NotFoundObjectResult.
[Fact]
public async Task ForSessionActionResult_ReturnsNotFoundObjectResultForNonexistentSession()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
var nonExistentSessionId = 999;
// Act
var result = await controller.ForSessionActionResult(nonExistentSessionId);
// Assert
var actionResult = Assert.IsType<ActionResult<List<IdeaDTO>>>(result);
Assert.IsType<NotFoundObjectResult>(actionResult.Result);
}
Für eine gültige Sitzungs-id bestätigt der zweite Test, dass die Methode Folgendes zurückgibt:
- Ein
ActionResultmit einemList<IdeaDTO>-Typ. -
ActionResult<T>.Value ist ein
List<IdeaDTO>-Typ. - Das erste Element in der Liste ist eine gültige Idee, die der in der Pseudositzung gespeicherten Idee entspricht (abgerufen durch den Aufruf von
GetTestSession).
[Fact]
public async Task ForSessionActionResult_ReturnsIdeasForSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSession());
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.ForSessionActionResult(testSessionId);
// Assert
var actionResult = Assert.IsType<ActionResult<List<IdeaDTO>>>(result);
var returnValue = Assert.IsType<List<IdeaDTO>>(actionResult.Value);
var idea = returnValue.FirstOrDefault();
Assert.Equal("One", idea.Name);
}
Die Beispiel-App enthält auch eine Methode zum Erstellen einer neuen Idea für eine bestimmte Sitzung. Der Controller gibt Folgendes zurück:
- BadRequest für ein ungültiges Modell.
- NotFound, wenn die Sitzung nicht existiert.
- CreatedAtAction, wenn die Sitzung mit der neuen Idee aktualisiert wird.
[HttpPost("createactionresult")]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
public async Task<ActionResult<BrainstormSession>> CreateActionResult([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var session = await _sessionRepository.GetByIdAsync(model.SessionId);
if (session == null)
{
return NotFound(model.SessionId);
}
var idea = new Idea()
{
DateCreated = DateTimeOffset.Now,
Description = model.Description,
Name = model.Name
};
session.AddIdea(idea);
await _sessionRepository.UpdateAsync(session);
return CreatedAtAction(nameof(CreateActionResult), new { id = session.Id }, session);
}
Drei Tests von CreateActionResult sind in ApiIdeasControllerTests enthalten.
Der erste Text bestätigt, dass eine BadRequest für ein ungültiges Modell zurückgegeben wird.
[Fact]
public async Task CreateActionResult_ReturnsBadRequest_GivenInvalidModel()
{
// Arrange & Act
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
controller.ModelState.AddModelError("error", "some error");
// Act
var result = await controller.CreateActionResult(model: null);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
Assert.IsType<BadRequestObjectResult>(actionResult.Result);
}
Der zweite Test überprüft, ob ein NotFound zurückgegeben wird, wenn die Sitzung nicht vorhanden ist.
[Fact]
public async Task CreateActionResult_ReturnsNotFoundObjectResultForNonexistentSession()
{
// Arrange
var nonExistentSessionId = 999;
string testName = "test name";
string testDescription = "test description";
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
var newIdea = new NewIdeaModel()
{
Description = testDescription,
Name = testName,
SessionId = nonExistentSessionId
};
// Act
var result = await controller.CreateActionResult(newIdea);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
Assert.IsType<NotFoundObjectResult>(actionResult.Result);
}
Für eine gültige Sitzungs-id bestätigt der letzte Test Folgendes:
- Die Methode gibt ein
ActionResultmit einemBrainstormSession-Typ zurück. -
ActionResult<T>.Result ist ein CreatedAtActionResult.
CreatedAtActionResultist analog zu einer 201 Created-Antwort mit einemLocation-Header. -
ActionResult<T>.Value ist ein
BrainstormSession-Typ. - Der Pseudoaufruf zum Aktualisieren der Sitzung (
UpdateAsync(testSession)) wurde aufgerufen. DerVerifiable-Methodenaufruf wird überprüft, indemmockRepo.Verify()in den Assertionen ausgeführt wird. - Zwei
Idea-Objekte werden für die Sitzung zurückgegeben. - Das letzte Element (die
Idea, die durch den PseudoaufrufUpdateAsynchinzugefügt wurde) stimmt mit dernewIdeaüberein, die der Sitzung im Test hinzugefügt wurde.
[Fact]
public async Task CreateActionResult_ReturnsNewlyCreatedIdeaForSession()
{
// Arrange
int testSessionId = 123;
string testName = "test name";
string testDescription = "test description";
var testSession = GetTestSession();
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(testSession);
var controller = new IdeasController(mockRepo.Object);
var newIdea = new NewIdeaModel()
{
Description = testDescription,
Name = testName,
SessionId = testSessionId
};
mockRepo.Setup(repo => repo.UpdateAsync(testSession))
.Returns(Task.CompletedTask)
.Verifiable();
// Act
var result = await controller.CreateActionResult(newIdea);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
var createdAtActionResult = Assert.IsType<CreatedAtActionResult>(actionResult.Result);
var returnValue = Assert.IsType<BrainstormSession>(createdAtActionResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnValue.Ideas.Count());
Assert.Equal(testName, returnValue.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnValue.Ideas.LastOrDefault().Description);
}
Controller spielen in jeder ASP.NET Core MVC-App eine zentrale Rolle. Daher sollten Sie sich auch darauf verlassen können, dass Controller wie beabsichtigt funktionieren. Automatisierte Tests können Fehler erkennen, bevor die App in einer Produktionsumgebung bereitgestellt wird.
Beispielcode anzeigen oder herunterladen (Vorgehensweise zum Herunterladen)
Unit-Tests der Controllerlogik
Unit-Tests beinhalten das Testen einer App-Komponente isoliert von ihrer Infrastruktur und ihren Abhängigkeiten. Bei einem Unit-Test der Controllerlogik werden nur die Inhalte einer einzelnen Aktion getestet, nicht das Verhalten ihrer Abhängigkeiten oder des Frameworks selbst.
Richten Sie Unit-Tests für Controlleraktionen ein, um sich auf das Verhalten des Controllers zu konzentrieren. Bei einem Komponententest des Controllers werden Szenarien wie Filter, Routing und Modellbindung vermieden. Tests, die die Interaktionen zwischen Komponenten betreffen, die gemeinsam auf eine Anforderung reagieren, werden durch Integrationstests abgedeckt. Weitere Informationen zu Integrationstests finden Sie unter Integrationstests in ASP.NET Core.
Wenn Sie benutzerdefinierte Filter und Routen schreiben, sollten Sie für diese isoliert einen Unit-Test durchführen, der nicht Bestandteil eines Tests für eine bestimmte Controlleraktion ist.
Um Unit-Tests für Controller zu demonstrieren, sehen Sie sich den folgenden Controller in der Beispiel-App an. Der Home-Controller zeigt eine Liste von Brainstormingsitzungen an und ermöglicht das Erstellen neuer Brainstormingsitzungen mit einer POST-Anforderung:
public class HomeController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;
public HomeController(IBrainstormSessionRepository sessionRepository)
{
_sessionRepository = sessionRepository;
}
public async Task<IActionResult> Index()
{
var sessionList = await _sessionRepository.ListAsync();
var model = sessionList.Select(session => new StormSessionViewModel()
{
Id = session.Id,
DateCreated = session.DateCreated,
Name = session.Name,
IdeaCount = session.Ideas.Count
});
return View(model);
}
public class NewSessionModel
{
[Required]
public string SessionName { get; set; }
}
[HttpPost]
public async Task<IActionResult> Index(NewSessionModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
else
{
await _sessionRepository.AddAsync(new BrainstormSession()
{
DateCreated = DateTimeOffset.Now,
Name = model.SessionName
});
}
return RedirectToAction(actionName: nameof(Index));
}
}
Für den oben aufgeführten Controller gilt Folgendes:
- Folgt dem Prinzip der expliziten Abhängigkeiten.
- Setzt dependency injection (DI) voraus, um eine Instanz von
IBrainstormSessionRepositorybereitzustellen. - Er kann mit einem simulierten
IBrainstormSessionRepository-Dienst mithilfe eines Mock-Objekt-Frameworks wie Moq getestet werden. Ein Mock-Objekt ist ein simuliertes Objekt mit vordefinierten Eigenschaften und Methoden, das zum Testen verwendet wird. Weitere Informationen finden Sie unter Einführung in Integrationstests.
Die HTTP GET Index-Methode enthält weder Schleifen noch Verzweigungen und ruft nur eine Methode auf. Der Komponententest für diese Aktion:
- Simuliert den
IBrainstormSessionRepository-Dienst mit derGetTestSessions-Methode.GetTestSessionserstellt zwei simulierte Brainstormingsitzungen mit Datumsangaben und Sitzungsnamen. - Führt die
Index-Methode aus. - Nimmt Assertionen für das von der Methode zurückgegebene Ergebnis vor:
- Ein ViewResult wird zurückgegeben.
- Das ViewDataDictionary.Model ist ein
StormSessionViewModel. - Es sind zwei Brainstormingsitzungen in
ViewDataDictionary.Modelgespeichert.
[Fact]
public async Task Index_ReturnsAViewResult_WithAListOfBrainstormSessions()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync())
.ReturnsAsync(GetTestSessions());
var controller = new HomeController(mockRepo.Object);
// Act
var result = await controller.Index();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<StormSessionViewModel>>(
viewResult.ViewData.Model);
Assert.Equal(2, model.Count());
}
private List<BrainstormSession> GetTestSessions()
{
var sessions = new List<BrainstormSession>();
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 2),
Id = 1,
Name = "Test One"
});
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 1),
Id = 2,
Name = "Test Two"
});
return sessions;
}
Die Tests der HTTP POST Index-Methode des Home-Controllers überprüfen, dass:
- Wenn ModelState.IsValid
falseist, gibt die Aktionsmethode den Status 400 Bad Request zurück (ViewResult mit den entsprechenden Daten). - Wenn
ModelState.IsValidtrueist:- Die
Add-Methode für das Repository wird aufgerufen. - Ein RedirectToActionResult wird mit den richtigen Argumenten zurückgegeben.
- Die
Ein ungültiger Modellstatus wird getestet, indem mithilfe von AddModelError (wie im ersten Test unten gezeigt) Fehler hinzugefügt werden:
[Fact]
public async Task IndexPost_ReturnsBadRequestResult_WhenModelStateIsInvalid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync())
.ReturnsAsync(GetTestSessions());
var controller = new HomeController(mockRepo.Object);
controller.ModelState.AddModelError("SessionName", "Required");
var newSession = new HomeController.NewSessionModel();
// Act
var result = await controller.Index(newSession);
// Assert
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
Assert.IsType<SerializableError>(badRequestResult.Value);
}
[Fact]
public async Task IndexPost_ReturnsARedirectAndAddsSession_WhenModelStateIsValid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.AddAsync(It.IsAny<BrainstormSession>()))
.Returns(Task.CompletedTask)
.Verifiable();
var controller = new HomeController(mockRepo.Object);
var newSession = new HomeController.NewSessionModel()
{
SessionName = "Test Name"
};
// Act
var result = await controller.Index(newSession);
// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
mockRepo.Verify();
}
Wenn ModelState nicht gültig ist, wird das gleiche ViewResult wie für eine GET-Anforderung zurückgegeben. Der Test versucht nicht, ein ungültiges Modell zu übergeben. Das Übergeben eines ungültigen Modells ist kein gültiger Ansatz, da die Modellbindung nicht ausgeführt wird (obwohl ein Integrationstest Modellbindung verwendet). In diesem Fall wird die Modellbindung nicht getestet. Diese Unit-Tests testen nur den Code in der Action-Methode.
Der zweite Test überprüft, dass der ModelState gültig ist:
-
BrainstormSessionwird neu hinzugefügt (über das Repository). - Die Methode gibt ein
RedirectToActionResultmit den erwarteten Eigenschaften zurück.
Simulierte Aufrufe, die nicht aufgerufen werden, werden normalerweise ignoriert. Durch Aufrufen von Verifiable am Ende des Einrichtungsaufrufs wird jedoch eine Mock-Validierung im Test ermöglicht. Dies erfolgt mit dem Aufruf von mockRepo.Verify. Damit schlägt der Test fehl, wenn die erwartete Methode nicht aufgerufen wurde.
Note
Mit der in diesem Beispiel verwendeten Moq-Bibliothek können überprüfbare (oder „strikte“) Pseudoobjekte mit nicht überprüfbaren Pseudoobjekten (auch „nicht-strikte“ Pseudoobjekte oder „Stubs“ genannt) kombiniert werden. Erfahren Sie mehr über das Anpassen des Verhaltens von Mock-Objekten mit Moq.
SessionController in der Beispiel-App zeigt Informationen zu einer bestimmten Brainstorming-Sitzung an. Der Controller enthält Logik zum Umgang mit ungültigen id-Werten (im folgenden Beispiel gibt es zwei return-Szenarien, um diese Fälle abzudecken). Die endgültige return-Befehl gibt ein neues StormSessionViewModel an die Ansicht zurück (Controllers/SessionController.cs):
public class SessionController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;
public SessionController(IBrainstormSessionRepository sessionRepository)
{
_sessionRepository = sessionRepository;
}
public async Task<IActionResult> Index(int? id)
{
if (!id.HasValue)
{
return RedirectToAction(actionName: nameof(Index),
controllerName: "Home");
}
var session = await _sessionRepository.GetByIdAsync(id.Value);
if (session == null)
{
return Content("Session not found.");
}
var viewModel = new StormSessionViewModel()
{
DateCreated = session.DateCreated,
Name = session.Name,
Id = session.Id
};
return View(viewModel);
}
}
Die Unit-Tests enthalten einen Test für jedes return-Szenario in der Index-Aktion des Sitzungscontrollers:
[Fact]
public async Task IndexReturnsARedirectToIndexHomeWhenIdIsNull()
{
// Arrange
var controller = new SessionController(sessionRepository: null);
// Act
var result = await controller.Index(id: null);
// Assert
var redirectToActionResult =
Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Home", redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
}
[Fact]
public async Task IndexReturnsContentWithSessionNotFoundWhenSessionNotFound()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new SessionController(mockRepo.Object);
// Act
var result = await controller.Index(testSessionId);
// Assert
var contentResult = Assert.IsType<ContentResult>(result);
Assert.Equal("Session not found.", contentResult.Content);
}
[Fact]
public async Task IndexReturnsViewResultWithStormSessionViewModel()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSessions().FirstOrDefault(
s => s.Id == testSessionId));
var controller = new SessionController(mockRepo.Object);
// Act
var result = await controller.Index(testSessionId);
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsType<StormSessionViewModel>(
viewResult.ViewData.Model);
Assert.Equal("Test One", model.Name);
Assert.Equal(2, model.DateCreated.Day);
Assert.Equal(testSessionId, model.Id);
}
Mit dem Wechsel zum Ideas-Controller stellt die App Funktionalität als Web-API auf der api/ideas-Route zur Verfügung:
- Eine Liste von Ideen (
IdeaDTO), die einer Brainstormingsitzung zugeordnet sind, wird durch dieForSession-Methode zurückgegeben. - Die
Create-Methode fügt einer Sitzung neue Ideen hinzu.
[HttpGet("forsession/{sessionId}")]
public async Task<IActionResult> ForSession(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}
var result = session.Ideas.Select(idea => new IdeaDTO()
{
Id = idea.Id,
Name = idea.Name,
Description = idea.Description,
DateCreated = idea.DateCreated
}).ToList();
return Ok(result);
}
[HttpPost("create")]
public async Task<IActionResult> Create([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var session = await _sessionRepository.GetByIdAsync(model.SessionId);
if (session == null)
{
return NotFound(model.SessionId);
}
var idea = new Idea()
{
DateCreated = DateTimeOffset.Now,
Description = model.Description,
Name = model.Name
};
session.AddIdea(idea);
await _sessionRepository.UpdateAsync(session);
return Ok(session);
}
Vermeiden Sie die Rückgabe von Geschäftsdomänenentitäten direkt über API-Aufrufe. Domänenentitäten:
- Enthalten häufig mehr Daten, als der Client benötigt.
- Das interne Domänenmodell der App unnötigerweise mit der öffentlich bereitgestellten API koppeln.
Die Zuordnung zwischen Domänenentitäten und den an den Client zurückgegebenen Typen kann durchgeführt werden:
- Manuell mit einer LINQ
Select, wie sie von der Beispiel-App verwendet wird. Weitere Informationen finden Sie unter LINQ (Language Integrated Query). - Automatisch mit einer Bibliothek, z.B. mit AutoMapper.
Anschließend demonstriert die Beispiel-App Unit-Tests für die API-Methoden Create und ForSession des Ideas-Controllers.
Die Beispiel-App enthält zwei ForSession-Tests. Der erste Test prüft, ob ForSession bei einer ungültigen Session ein NotFoundObjectResult (HTTP Not Found) zurückgibt:
[Fact]
public async Task ForSession_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.ForSession(testSessionId);
// Assert
var notFoundObjectResult = Assert.IsType<NotFoundObjectResult>(result);
Assert.Equal(testSessionId, notFoundObjectResult.Value);
}
Der zweite ForSession-Test ermittelt, ob ForSession eine Liste der Sitzungsideen (<List<IdeaDTO>>) für eine gültige Sitzung zurückgibt. Die Prüfungen untersuchen auch die erste Idee, um zu bestätigen, dass ihre Name-Eigenschaft richtig ist:
[Fact]
public async Task ForSession_ReturnsIdeasForSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSession());
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.ForSession(testSessionId);
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnValue = Assert.IsType<List<IdeaDTO>>(okResult.Value);
var idea = returnValue.FirstOrDefault();
Assert.Equal("One", idea.Name);
}
Um das Verhalten der Create-Methode zu testen, wenn ModelState ungültig ist, fügt die Beispiel-App dem Controller einen Modellfehler als Teil des Tests hinzu. Versuchen Sie nicht, die Modellvalidierung oder Modellbindung in Unit-Tests zu testen – testen Sie einfach das Verhalten der Aktionsmethode, wenn ihr ein ungültiges ModelState übergeben wird:
[Fact]
public async Task Create_ReturnsBadRequest_GivenInvalidModel()
{
// Arrange & Act
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
controller.ModelState.AddModelError("error", "some error");
// Act
var result = await controller.Create(model: null);
// Assert
Assert.IsType<BadRequestObjectResult>(result);
}
Der zweite Test in Create setzt voraus, dass das Repository null zurückgibt, daher wird das Mock-Repository so konfiguriert, dass es null zurückgibt. Es ist nicht erforderlich, eine Testdatenbank (im Arbeitsspeicher oder anderweitig) zu erstellen und eine Abfrage zu generieren, die dieses Ergebnis zurückgibt. Der Test kann mit einer einzigen Anweisung ausgeführt werden, wie der Beispielcode veranschaulicht:
[Fact]
public async Task Create_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.Create(new NewIdeaModel());
// Assert
Assert.IsType<NotFoundObjectResult>(result);
}
Im dritten Create-Test (Create_ReturnsNewlyCreatedIdeaForSession) wird überprüft, ob die UpdateAsync-Methode des Repositorys aufgerufen wird. Der Mock wird mit Verifiable aufgerufen. Anschließend wird die Verify-Methode des gemockten Repositorys aufgerufen, um zu bestätigen, dass die überprüfbare Methode ausgeführt wird. Sicherzustellen, dass die Daten von der UpdateAsync-Methode gespeichert wurden, gehört nicht zu den Aufgaben des Unit-Tests. Dies kann mit einem Integrationstest bestätigt werden.
[Fact]
public async Task Create_ReturnsNewlyCreatedIdeaForSession()
{
// Arrange
int testSessionId = 123;
string testName = "test name";
string testDescription = "test description";
var testSession = GetTestSession();
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(testSession);
var controller = new IdeasController(mockRepo.Object);
var newIdea = new NewIdeaModel()
{
Description = testDescription,
Name = testName,
SessionId = testSessionId
};
mockRepo.Setup(repo => repo.UpdateAsync(testSession))
.Returns(Task.CompletedTask)
.Verifiable();
// Act
var result = await controller.Create(newIdea);
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnSession.Ideas.Count());
Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnSession.Ideas.LastOrDefault().Description);
}
Test ActionResult<T>
In ASP.NET Core 2.1 oder höher ermöglicht es ActionResult<T> (ActionResult<TValue>) Ihnen, einen Typ, der von ActionResult abgeleitet ist, oder einen bestimmten Typ zurückzugeben.
Die Beispielanwendung enthält eine Methode, die ein List<IdeaDTO> für eine bestimmte Sitzung id zurückgibt. Wenn die Sitzung id nicht vorhanden ist, gibt der Controller NotFound zurück:
[HttpGet("forsessionactionresult/{sessionId}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<ActionResult<List<IdeaDTO>>> ForSessionActionResult(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}
var result = session.Ideas.Select(idea => new IdeaDTO()
{
Id = idea.Id,
Name = idea.Name,
Description = idea.Description,
DateCreated = idea.DateCreated
}).ToList();
return result;
}
Zwei Tests des ForSessionActionResult-Controllers sind in ApiIdeasControllerTests vorhanden.
Der erste Test bestätigt, dass der Controller ein ActionResult zurückgibt, aber keine nicht existierende Liste von Ideen für eine nicht existierende Sitzung id:
- Der Typ von
ActionResultistActionResult<List<IdeaDTO>>. - Result ist ein NotFoundObjectResult.
[Fact]
public async Task ForSessionActionResult_ReturnsNotFoundObjectResultForNonexistentSession()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
var nonExistentSessionId = 999;
// Act
var result = await controller.ForSessionActionResult(nonExistentSessionId);
// Assert
var actionResult = Assert.IsType<ActionResult<List<IdeaDTO>>>(result);
Assert.IsType<NotFoundObjectResult>(actionResult.Result);
}
Für eine gültige Sitzung id bestätigt der zweite Test, dass die Methode Folgendes zurückgibt:
- Ein
ActionResultmit einemList<IdeaDTO>-Typ. -
ActionResult<T>.Value ist vom Typ
List<IdeaDTO>. - Das erste Element in der Liste ist eine gültige Idee, die der in der Pseudositzung gespeicherten Idee entspricht (abgerufen durch den Aufruf von
GetTestSession).
[Fact]
public async Task ForSessionActionResult_ReturnsIdeasForSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSession());
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.ForSessionActionResult(testSessionId);
// Assert
var actionResult = Assert.IsType<ActionResult<List<IdeaDTO>>>(result);
var returnValue = Assert.IsType<List<IdeaDTO>>(actionResult.Value);
var idea = returnValue.FirstOrDefault();
Assert.Equal("One", idea.Name);
}
Die Beispiel-App enthält auch eine Methode zum Erstellen einer neuen Idea für eine bestimmte Sitzung. Der Controller gibt Folgendes zurück:
- BadRequest für ein ungültiges Modell.
- NotFound falls die Sitzung nicht vorhanden ist.
- CreatedAtAction, wenn die Sitzung mit der neuen Idee aktualisiert wird.
[HttpPost("createactionresult")]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
public async Task<ActionResult<BrainstormSession>> CreateActionResult([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var session = await _sessionRepository.GetByIdAsync(model.SessionId);
if (session == null)
{
return NotFound(model.SessionId);
}
var idea = new Idea()
{
DateCreated = DateTimeOffset.Now,
Description = model.Description,
Name = model.Name
};
session.AddIdea(idea);
await _sessionRepository.UpdateAsync(session);
return CreatedAtAction(nameof(CreateActionResult), new { id = session.Id }, session);
}
Drei Tests zu CreateActionResult sind in ApiIdeasControllerTests enthalten.
Der erste Test bestätigt, dass eine BadRequest für ein ungültiges Modell zurückgegeben wird.
[Fact]
public async Task CreateActionResult_ReturnsBadRequest_GivenInvalidModel()
{
// Arrange & Act
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
controller.ModelState.AddModelError("error", "some error");
// Act
var result = await controller.CreateActionResult(model: null);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
Assert.IsType<BadRequestObjectResult>(actionResult.Result);
}
Der zweite Test überprüft, ob ein NotFound zurückgegeben wird, wenn die Sitzung nicht vorhanden ist.
[Fact]
public async Task CreateActionResult_ReturnsNotFoundObjectResultForNonexistentSession()
{
// Arrange
var nonExistentSessionId = 999;
string testName = "test name";
string testDescription = "test description";
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
var newIdea = new NewIdeaModel()
{
Description = testDescription,
Name = testName,
SessionId = nonExistentSessionId
};
// Act
var result = await controller.CreateActionResult(newIdea);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
Assert.IsType<NotFoundObjectResult>(actionResult.Result);
}
Für eine gültige Sitzung id bestätigt der letzte Test Folgendes:
- Die Methode gibt ein
ActionResultmit einemBrainstormSession-Typ zurück. -
ActionResult<T>.Result ist ein CreatedAtActionResult.
CreatedAtActionResultentspricht einer 201 Created-Antwort mit einemLocation-Header. -
ActionResult<T>.Value ist ein
BrainstormSession-Typ. - Der Mock-Aufruf zum Aktualisieren der Sitzung (
UpdateAsync(testSession)) wurde aufgerufen. DerVerifiable-Methodenaufruf wird überprüft, indemmockRepo.Verify()in den Assertionen ausgeführt wird. - Zwei
Idea-Objekte werden für die Sitzung zurückgegeben. - Das letzte Element (das
Idea, das durch den Mock-Aufruf vonUpdateAsynchinzugefügt wurde) entspricht demnewIdea, das im Test zur Sitzung hinzugefügt wurde.
[Fact]
public async Task CreateActionResult_ReturnsNewlyCreatedIdeaForSession()
{
// Arrange
int testSessionId = 123;
string testName = "test name";
string testDescription = "test description";
var testSession = GetTestSession();
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(testSession);
var controller = new IdeasController(mockRepo.Object);
var newIdea = new NewIdeaModel()
{
Description = testDescription,
Name = testName,
SessionId = testSessionId
};
mockRepo.Setup(repo => repo.UpdateAsync(testSession))
.Returns(Task.CompletedTask)
.Verifiable();
// Act
var result = await controller.CreateActionResult(newIdea);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>(result);
var createdAtActionResult = Assert.IsType<CreatedAtActionResult>(actionResult.Result);
var returnValue = Assert.IsType<BrainstormSession>(createdAtActionResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnValue.Ideas.Count());
Assert.Equal(testName, returnValue.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnValue.Ideas.LastOrDefault().Description);
}
Verwandte Inhalte
- Integrationstests in ASP.NET Core
- Erstellen und Ausführen von Komponententests mit Visual Studio
- MyTested.AspNetCore.Mvc - Fluent Testing-Bibliothek für ASP.NET Core MVC: stark typisierte Bibliothek für Komponententests mit einer Fluent-Schnittstelle zum Testen von MVC- und Web-API-Apps. (Wird von Microsoft nicht verwaltet oder unterstützt. )
- JustMockLite: Ein Mocking-Framework für .NET-Entwickler. (Wird von Microsoft nicht verwaltet oder unterstützt. )