Logika kontrolera testów jednostkowych w systemie ASP.NET Core
Autor: Steve Smith
Testy jednostkowe obejmują testowanie części aplikacji w izolacji od jej infrastruktury i zależności. W przypadku logiki kontrolera testów jednostkowych sprawdzana jest tylko zawartość pojedynczej akcji, a nie zachowanie jego zależności ani samej struktury.
Kontrolery testów jednostkowych
Skonfiguruj testy jednostkowe akcji kontrolera, aby skoncentrować się na zachowaniu kontrolera. Test jednostkowy kontrolera unika scenariuszy, takich jak filtry, routing i powiązanie modelu. Testy obejmujące interakcje między składnikami, które wspólnie odpowiadają na żądanie, są obsługiwane przez testy integracji. Aby uzyskać więcej informacji na temat testów integracji, zobacz Testy integracji w programie ASP.NET Core.
Jeśli piszesz niestandardowe filtry i trasy, przetestuj je w izolacji, a nie w ramach testów dla określonej akcji kontrolera.
Aby zademonstrować testy jednostkowe kontrolera, zapoznaj się z następującym kontrolerem w przykładowej aplikacji.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Kontroler Home wyświetla listę sesji burzy mózgów i umożliwia tworzenie nowych sesji burzy mózgów z żądaniem POST:
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));
}
}
Poprzedni kontroler:
- Jest zgodna z zasadą jawnych zależności.
- Oczekuje iniekcji zależności (DI), aby zapewnić wystąpienie
IBrainstormSessionRepository
klasy . - Można przetestować za pomocą pozorowanej
IBrainstormSessionRepository
usługi przy użyciu makiety struktury obiektów, takiej jak Moq. Pozorowany obiekt jest obiektem skonstruowanym ze wstępnie określonym zestawem zachowań właściwości i metod używanych do testowania. Aby uzyskać więcej informacji, zobacz Wprowadzenie do testów integracji.
Metoda HTTP GET Index
nie ma pętli ani rozgałęziania i wywołuje tylko jedną metodę. Test jednostkowy dla tej akcji:
- Wyśmiewa usługę
IBrainstormSessionRepository
GetTestSessions
przy użyciu metody .GetTestSessions
Tworzy dwie makiety sesji burzy mózgów z datami i nazwami sesji. - Wykonuje metodę
Index
. - Wykonuje asercji w wyniku zwróconym przez metodę:
- Zwracany jest element A ViewResult .
- Element ViewDataDictionary.Model to
StormSessionViewModel
. - Istnieją dwie sesje burzy mózgów przechowywane w pliku
ViewDataDictionary.Model
.
[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;
}
Testy Home metody kontrolera HTTP POST Index
sprawdza, czy:
- Gdy parametr ModelState.IsValid to
false
, metoda akcji zwraca 400 Nieprawidłowe żądanie ViewResult z odpowiednimi danymi. - Gdy
ModelState.IsValid
ma wartośćtrue
:- Wywoływana
Add
jest metoda w repozytorium. - Element A RedirectToActionResult jest zwracany z poprawnymi argumentami.
- Wywoływana
Nieprawidłowy stan modelu jest testowany przez dodanie błędów, jak AddModelError pokazano w pierwszym teście poniżej:
[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();
}
Jeśli parametr ModelState jest nieprawidłowy, jest zwracany taki sam ViewResult
jak w przypadku żądania GET. Test nie próbuje przejść w nieprawidłowym modelu. Przekazywanie nieprawidłowego modelu nie jest prawidłowym podejściem, ponieważ powiązanie modelu nie jest uruchomione (chociaż test integracji używa powiązania modelu). W takim przypadku powiązanie modelu nie jest testowane. Te testy jednostkowe testują tylko kod w metodzie akcji.
Drugi test sprawdza, czy gdy ModelState
element jest prawidłowy:
- Zostanie dodany nowy
BrainstormSession
(za pośrednictwem repozytorium). - Metoda zwraca
RedirectToActionResult
obiekt z oczekiwanymi właściwościami.
Wyśmiewane wywołania, które nie są wywoływane, są zwykle ignorowane, ale wywołanie Verifiable
na końcu wywołania konfiguracji umożliwia pozorowanie weryfikacji w teście. Jest to wykonywane za pomocą wywołania metody mockRepo.Verify
, która kończy się niepowodzeniem testu, jeśli oczekiwana metoda nie została wywołana.
Uwaga
Biblioteka Moq używana w tym przykładzie umożliwia mieszanie weryfikowalnych lub "surowych" makiety z niemożliwymi do zweryfikowania makiety (nazywane również "luźnymi" makietami lub wycinkami). Dowiedz się więcej na temat dostosowywania zachowania makiety za pomocą narzędzia Moq.
SessionController w przykładowej aplikacji wyświetla informacje związane z określoną sesją burzy mózgów. Kontroler zawiera logikę do obsługi nieprawidłowych id
wartości (istnieją dwa return
scenariusze w poniższym przykładzie, aby uwzględnić te scenariusze). return
Końcowa instrukcja zwraca nowy StormSessionViewModel
widok (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);
}
}
Testy jednostkowe obejmują jeden test dla każdego return
scenariusza w akcji Kontroler Index
sesji:
[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);
}
Przejście do kontrolera Ideas umożliwia aplikacji uwidacznianie funkcji jako internetowego interfejsu API na api/ideas
trasie:
- Lista pomysłów (
IdeaDTO
) skojarzonych z sesją burzy mózgów jest zwracana przez metodęForSession
. - Metoda
Create
dodaje nowe pomysły do sesji.
[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);
}
Unikaj zwracania jednostek domeny biznesowej bezpośrednio za pośrednictwem wywołań interfejsu API. Jednostki domeny:
- Często zawierają więcej danych niż wymaga klient.
- Niepotrzebnie połącz wewnętrzny model domeny aplikacji z publicznie uwidocznionymi interfejsem API.
Mapowanie między jednostkami domeny a typami zwracanym do klienta można wykonać:
- Ręcznie za pomocą linQ
Select
, jak używa przykładowa aplikacja. Aby uzyskać więcej informacji, zobacz LINQ (Zapytanie zintegrowane w języku). - Automatycznie z biblioteką, taką jak AutoMapper.
Następnie przykładowa aplikacja demonstruje testy jednostkowe metod Create
i ForSession
interfejsu API kontrolera Ideas.
Przykładowa aplikacja zawiera dwa ForSession
testy. Pierwszy test określa, czy ForSession
zwraca wartość NotFoundObjectResult (nie znaleziono protokołu HTTP) dla nieprawidłowej sesji:
[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);
}
Drugi ForSession
test określa, czy ForSession
zwraca listę pomysłów sesji (<List<IdeaDTO>>
) dla prawidłowej sesji. Kontrole sprawdzają również pierwszy pomysł, aby potwierdzić, że jego Name
właściwość jest poprawna:
[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);
}
Aby przetestować zachowanie Create
metody, gdy ModelState
parametr jest nieprawidłowy, przykładowa aplikacja dodaje błąd modelu do kontrolera w ramach testu. Nie próbuj testować walidacji modelu ani powiązania modelu w testach jednostkowych — po prostu przetestuj zachowanie metody akcji w przypadku konfrontacji z nieprawidłowym ModelState
elementem :
[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);
}
Drugi test Create
funkcji zależy od zwracanego null
repozytorium , więc repozytorium makietowe jest skonfigurowane tak, aby zwracało wartość null
. Nie ma potrzeby tworzenia testowej bazy danych (w pamięci lub w inny sposób) i konstruowania zapytania zwracającego ten wynik. Test można wykonać w jednej instrukcji, jak pokazano w przykładowym kodzie:
[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);
}
Trzeci Create
test sprawdza, Create_ReturnsNewlyCreatedIdeaForSession
czy metoda repozytorium jest wywoływana UpdateAsync
. Pozorowana metoda jest wywoływana za pomocą Verifiable
metody , a metoda wyśmiewanego repozytorium Verify
jest wywoływana w celu potwierdzenia wykonania metody weryfikowalnej. Nie jest to odpowiedzialność za test jednostkowy w celu zapewnienia, że UpdateAsync
metoda zapisała dane — które można wykonać z testem integracji.
[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);
}
Akcja testowaResult<T>
ActionResult<T> (ActionResult<TValue>) może zwrócić typ pochodzący z ActionResult
lub zwrócić określony typ.
Przykładowa aplikacja zawiera metodę zwracającą List<IdeaDTO>
element dla danej sesji id
. Jeśli sesja id
nie istnieje, kontroler zwraca polecenie NotFound:
[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;
}
W obiekcie ApiIdeasControllerTests
znajdują się dwa testy ForSessionActionResult
kontrolera .
Pierwszy test potwierdza, że kontroler zwraca nieistniejącą ActionResult
listę pomysłów na nieistniejącą sesję id
:
- Typ
ActionResult
toActionResult<List<IdeaDTO>>
. - Element Result to 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);
}
W przypadku prawidłowej sesji id
drugi test potwierdza, że metoda zwraca następujące elementy:
- Element
ActionResult
z typemList<IdeaDTO>
. - AkcjaResult <T>. Wartość jest typem
List<IdeaDTO>
. - Pierwszy element na liście jest prawidłowym pomysłem zgodnym z ideą przechowywaną w pozornej sesji (uzyskanej przez wywołanie metody
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);
}
Przykładowa aplikacja zawiera również metodę tworzenia nowej Idea
dla danej sesji. Kontroler zwraca następujące dane:
- BadRequest dla nieprawidłowego modelu.
- NotFound jeśli sesja nie istnieje.
- CreatedAtAction gdy sesja zostanie zaktualizowana o nowy pomysł.
[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);
}
W elemecie znajdują się trzy testy CreateActionResult
.ApiIdeasControllerTests
Pierwszy tekst potwierdza, że BadRequest element jest zwracany dla nieprawidłowego modelu.
[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);
}
Drugi test sprawdza, czy element jest zwracany, NotFound jeśli sesja nie istnieje.
[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);
}
W przypadku prawidłowej sesji id
końcowy test potwierdza, że:
- Metoda zwraca element
ActionResult
o typieBrainstormSession
. - AkcjaResult <T>. Wynik to CreatedAtActionResult.
CreatedAtActionResult
jest analogiczny do odpowiedzi 201 Utworzono z nagłówkiemLocation
. - AkcjaResult <T>. Wartość jest typem
BrainstormSession
. - Wywołanie makiety w celu zaktualizowania sesji ,
UpdateAsync(testSession)
zostało wywołane. WywołanieVerifiable
metody jest sprawdzane przez wykonaniemockRepo.Verify()
w asercji. - Dla sesji są zwracane dwa
Idea
obiekty. - Ostatni element (
Idea
dodany przez wywołanie makiety doUpdateAsync
elementu ) jest zgodnynewIdea
z dodanym elementem do sesji w teście.
[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);
}
Kontrolery odgrywają kluczową rolę w dowolnej aplikacji MVC platformy ASP.NET Core. W związku z tym należy mieć pewność, że kontrolery zachowują się zgodnie z oczekiwaniami. Testy automatyczne mogą wykrywać błędy przed wdrożeniem aplikacji w środowisku produkcyjnym.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Testy jednostkowe logiki kontrolera
Testy jednostkowe obejmują testowanie części aplikacji w izolacji od jej infrastruktury i zależności. W przypadku logiki kontrolera testów jednostkowych sprawdzana jest tylko zawartość pojedynczej akcji, a nie zachowanie jego zależności ani samej struktury.
Skonfiguruj testy jednostkowe akcji kontrolera, aby skoncentrować się na zachowaniu kontrolera. Test jednostkowy kontrolera unika scenariuszy, takich jak filtry, routing i powiązanie modelu. Testy obejmujące interakcje między składnikami, które wspólnie odpowiadają na żądanie, są obsługiwane przez testy integracji. Aby uzyskać więcej informacji na temat testów integracji, zobacz Testy integracji w programie ASP.NET Core.
Jeśli piszesz niestandardowe filtry i trasy, przetestuj je w izolacji, a nie w ramach testów dla określonej akcji kontrolera.
Aby zademonstrować testy jednostkowe kontrolera, zapoznaj się z następującym kontrolerem w przykładowej aplikacji. Kontroler Home wyświetla listę sesji burzy mózgów i umożliwia tworzenie nowych sesji burzy mózgów z żądaniem POST:
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));
}
}
Poprzedni kontroler:
- Jest zgodna z zasadą jawnych zależności.
- Oczekuje iniekcji zależności (DI), aby zapewnić wystąpienie
IBrainstormSessionRepository
klasy . - Można przetestować za pomocą pozorowanej
IBrainstormSessionRepository
usługi przy użyciu makiety struktury obiektów, takiej jak Moq. Pozorowany obiekt jest obiektem skonstruowanym ze wstępnie określonym zestawem zachowań właściwości i metod używanych do testowania. Aby uzyskać więcej informacji, zobacz Wprowadzenie do testów integracji.
Metoda HTTP GET Index
nie ma pętli ani rozgałęziania i wywołuje tylko jedną metodę. Test jednostkowy dla tej akcji:
- Wyśmiewa usługę
IBrainstormSessionRepository
GetTestSessions
przy użyciu metody .GetTestSessions
Tworzy dwie makiety sesji burzy mózgów z datami i nazwami sesji. - Wykonuje metodę
Index
. - Wykonuje asercji w wyniku zwróconym przez metodę:
- Zwracany jest element A ViewResult .
- Element ViewDataDictionary.Model to
StormSessionViewModel
. - Istnieją dwie sesje burzy mózgów przechowywane w pliku
ViewDataDictionary.Model
.
[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;
}
Testy Home metody kontrolera HTTP POST Index
sprawdza, czy:
- Gdy parametr ModelState.IsValid to
false
, metoda akcji zwraca 400 Nieprawidłowe żądanie ViewResult z odpowiednimi danymi. - Gdy
ModelState.IsValid
ma wartośćtrue
:- Wywoływana
Add
jest metoda w repozytorium. - Element A RedirectToActionResult jest zwracany z poprawnymi argumentami.
- Wywoływana
Nieprawidłowy stan modelu jest testowany przez dodanie błędów, jak AddModelError pokazano w pierwszym teście poniżej:
[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();
}
Jeśli parametr ModelState jest nieprawidłowy, jest zwracany taki sam ViewResult
jak w przypadku żądania GET. Test nie próbuje przejść w nieprawidłowym modelu. Przekazywanie nieprawidłowego modelu nie jest prawidłowym podejściem, ponieważ powiązanie modelu nie jest uruchomione (chociaż test integracji używa powiązania modelu). W takim przypadku powiązanie modelu nie jest testowane. Te testy jednostkowe testują tylko kod w metodzie akcji.
Drugi test sprawdza, czy gdy ModelState
element jest prawidłowy:
- Zostanie dodany nowy
BrainstormSession
(za pośrednictwem repozytorium). - Metoda zwraca
RedirectToActionResult
obiekt z oczekiwanymi właściwościami.
Wyśmiewane wywołania, które nie są wywoływane, są zwykle ignorowane, ale wywołanie Verifiable
na końcu wywołania konfiguracji umożliwia pozorowanie weryfikacji w teście. Jest to wykonywane za pomocą wywołania metody mockRepo.Verify
, która kończy się niepowodzeniem testu, jeśli oczekiwana metoda nie została wywołana.
Uwaga
Biblioteka Moq używana w tym przykładzie umożliwia mieszanie weryfikowalnych lub "surowych" makiety z niemożliwymi do zweryfikowania makiety (nazywane również "luźnymi" makietami lub wycinkami). Dowiedz się więcej na temat dostosowywania zachowania makiety za pomocą narzędzia Moq.
SessionController w przykładowej aplikacji wyświetla informacje związane z określoną sesją burzy mózgów. Kontroler zawiera logikę do obsługi nieprawidłowych id
wartości (istnieją dwa return
scenariusze w poniższym przykładzie, aby uwzględnić te scenariusze). return
Końcowa instrukcja zwraca nowy StormSessionViewModel
widok (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);
}
}
Testy jednostkowe obejmują jeden test dla każdego return
scenariusza w akcji Kontroler Index
sesji:
[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);
}
Przejście do kontrolera Ideas umożliwia aplikacji uwidacznianie funkcji jako internetowego interfejsu API na api/ideas
trasie:
- Lista pomysłów (
IdeaDTO
) skojarzonych z sesją burzy mózgów jest zwracana przez metodęForSession
. - Metoda
Create
dodaje nowe pomysły do sesji.
[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);
}
Unikaj zwracania jednostek domeny biznesowej bezpośrednio za pośrednictwem wywołań interfejsu API. Jednostki domeny:
- Często zawierają więcej danych niż wymaga klient.
- Niepotrzebnie połącz wewnętrzny model domeny aplikacji z publicznie uwidocznionymi interfejsem API.
Mapowanie między jednostkami domeny a typami zwracanym do klienta można wykonać:
- Ręcznie za pomocą linQ
Select
, jak używa przykładowa aplikacja. Aby uzyskać więcej informacji, zobacz LINQ (Zapytanie zintegrowane w języku). - Automatycznie z biblioteką, taką jak AutoMapper.
Następnie przykładowa aplikacja demonstruje testy jednostkowe metod Create
i ForSession
interfejsu API kontrolera Ideas.
Przykładowa aplikacja zawiera dwa ForSession
testy. Pierwszy test określa, czy ForSession
zwraca wartość NotFoundObjectResult (nie znaleziono protokołu HTTP) dla nieprawidłowej sesji:
[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);
}
Drugi ForSession
test określa, czy ForSession
zwraca listę pomysłów sesji (<List<IdeaDTO>>
) dla prawidłowej sesji. Kontrole sprawdzają również pierwszy pomysł, aby potwierdzić, że jego Name
właściwość jest poprawna:
[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);
}
Aby przetestować zachowanie Create
metody, gdy ModelState
parametr jest nieprawidłowy, przykładowa aplikacja dodaje błąd modelu do kontrolera w ramach testu. Nie próbuj testować walidacji modelu ani powiązania modelu w testach jednostkowych — po prostu przetestuj zachowanie metody akcji w przypadku konfrontacji z nieprawidłowym ModelState
elementem :
[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);
}
Drugi test Create
funkcji zależy od zwracanego null
repozytorium , więc repozytorium makietowe jest skonfigurowane tak, aby zwracało wartość null
. Nie ma potrzeby tworzenia testowej bazy danych (w pamięci lub w inny sposób) i konstruowania zapytania zwracającego ten wynik. Test można wykonać w jednej instrukcji, jak pokazano w przykładowym kodzie:
[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);
}
Trzeci Create
test sprawdza, Create_ReturnsNewlyCreatedIdeaForSession
czy metoda repozytorium jest wywoływana UpdateAsync
. Pozorowana metoda jest wywoływana za pomocą Verifiable
metody , a metoda wyśmiewanego repozytorium Verify
jest wywoływana w celu potwierdzenia wykonania metody weryfikowalnej. Nie jest to odpowiedzialność za test jednostkowy w celu zapewnienia, że UpdateAsync
metoda zapisała dane — które można wykonać z testem integracji.
[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);
}
Akcja testowaResult<T>
W systemie ASP.NET Core 2.1 lub nowszym funkcja ActionResult<T> (ActionResult<TValue>) umożliwia zwrócenie typu pochodzącego z ActionResult
lub zwrócenia określonego typu.
Przykładowa aplikacja zawiera metodę zwracającą List<IdeaDTO>
element dla danej sesji id
. Jeśli sesja id
nie istnieje, kontroler zwraca polecenie NotFound:
[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;
}
W obiekcie ApiIdeasControllerTests
znajdują się dwa testy ForSessionActionResult
kontrolera .
Pierwszy test potwierdza, że kontroler zwraca nieistniejącą ActionResult
listę pomysłów na nieistniejącą sesję id
:
- Typ
ActionResult
toActionResult<List<IdeaDTO>>
. - Element Result to 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);
}
W przypadku prawidłowej sesji id
drugi test potwierdza, że metoda zwraca następujące elementy:
- Element
ActionResult
z typemList<IdeaDTO>
. - AkcjaResult <T>. Wartość jest typem
List<IdeaDTO>
. - Pierwszy element na liście jest prawidłowym pomysłem zgodnym z ideą przechowywaną w pozornej sesji (uzyskanej przez wywołanie metody
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);
}
Przykładowa aplikacja zawiera również metodę tworzenia nowej Idea
dla danej sesji. Kontroler zwraca następujące dane:
- BadRequest dla nieprawidłowego modelu.
- NotFound jeśli sesja nie istnieje.
- CreatedAtAction gdy sesja zostanie zaktualizowana o nowy pomysł.
[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);
}
W elemecie znajdują się trzy testy CreateActionResult
.ApiIdeasControllerTests
Pierwszy tekst potwierdza, że BadRequest element jest zwracany dla nieprawidłowego modelu.
[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);
}
Drugi test sprawdza, czy element jest zwracany, NotFound jeśli sesja nie istnieje.
[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);
}
W przypadku prawidłowej sesji id
końcowy test potwierdza, że:
- Metoda zwraca element
ActionResult
o typieBrainstormSession
. - AkcjaResult <T>. Wynik to CreatedAtActionResult.
CreatedAtActionResult
jest analogiczny do odpowiedzi 201 Utworzono z nagłówkiemLocation
. - AkcjaResult <T>. Wartość jest typem
BrainstormSession
. - Wywołanie makiety w celu zaktualizowania sesji ,
UpdateAsync(testSession)
zostało wywołane. WywołanieVerifiable
metody jest sprawdzane przez wykonaniemockRepo.Verify()
w asercji. - Dla sesji są zwracane dwa
Idea
obiekty. - Ostatni element (
Idea
dodany przez wywołanie makiety doUpdateAsync
elementu ) jest zgodnynewIdea
z dodanym elementem do sesji w teście.
[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);
}
Dodatkowe zasoby
- Testy integracji w programie ASP.NET Core
- Tworzenie i uruchamianie testów jednostkowych za pomocą programu Visual Studio
- MyTest.AspNetCore.Mvc — biblioteka fluent testing library for ASP.NET Core MVC: silnie typizowana biblioteka testów jednostkowych, zapewniając płynny interfejs do testowania aplikacji MVC i internetowych interfejsów API. (Nieobsługiwane lub obsługiwane przez firmę Microsoft).
- JustMockLite: pozorna struktura dla deweloperów platformy .NET. (Nieobsługiwane lub obsługiwane przez firmę Microsoft).