Události
Mistrovství Světa v Power BI DataViz
14. 2. 16 - 31. 3. 16
Se 4 šance na vstup, můžete vyhrát konferenční balíček a udělat to na LIVE Grand Finale v Las Vegas
Další informaceTento prohlížeč se už nepodporuje.
Upgradujte na Microsoft Edge, abyste mohli využívat nejnovější funkce, aktualizace zabezpečení a technickou podporu.
Autor: Steve Smith
Testy jednotek zahrnují testování části aplikace izolovaně od infrastruktury a závislostí. Pokud je logika kontroleru testování jednotek, testují se pouze obsah jedné akce, nikoli chování jejích závislostí nebo samotné architektury.
Nastavte testy jednotek akcí kontroleru, abyste se mohli zaměřit na chování kontroleru. Test jednotek kontroleru zabraňuje scénářům, jako jsou filtry, směrování a vazby modelu. Testy, které pokrývají interakce mezi komponentami, které souhrnně reagují na požadavek, se zpracovávají integračními testy. Další informace o integračníchtestch ASP.NET ch
Pokud píšete vlastní filtry a trasy, otestujte je izolovaně, ne jako součást testů na konkrétní akci kontroleru.
Pokud chcete předvést testy jednotek kontroleru, projděte si následující kontroler v ukázkové aplikaci.
Zobrazení nebo stažení ukázkového kódu (postup stažení)
Kontroler Home zobrazí seznam debat a umožňuje vytváření nových debat s požadavkem 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));
}
}
Předchozí kontroler:
IBrainstormSessionRepository
.IBrainstormSessionRepository
architektury objektů, jako je Například Moq. Napodobený objekt je objekt s předem určenou sadou vlastností a chování metod používaných k testování. Další informace naleznete v tématu Úvod do integračních testů.Metoda HTTP GET Index
nemá žádnou smyčku ani větvení a volá pouze jednu metodu. Test jednotek pro tuto akci:
IBrainstormSessionRepository
službu pomocí GetTestSessions
metody. GetTestSessions
vytvoří dvě napodobené debaty s daty a názvy relací.Index
.StormSessionViewModel
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;
}
Metoda Home kontroleru HTTP POST Index
ověřuje, že:
false
, metoda akce vrátí 400 Chybný požadavek ViewResult s příslušnými daty.ModelState.IsValid
je true
: Add
metoda v úložišti.Neplatný stav modelu se testuje přidáním chyb, AddModelError jak je znázorněno v prvním testu níže:
[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();
}
Pokud modelState není platný, vrátí se totéž ViewResult
jako u požadavku GET. Test se nepokouší předat neplatný model. Předání neplatného modelu není platný přístup, protože vazba modelu není spuštěná (i když integrační test používá vazbu modelu). V tomto případě se vazba modelu neotestuje. Tyto testy jednotek testují pouze kód v metodě akce.
Druhý test ověří, že pokud ModelState
je platný:
BrainstormSession
(prostřednictvím úložiště).RedirectToActionResult
hodnotu s očekávanými vlastnostmi.Volání napodobená volání, která nejsou volána, se obvykle ignorují, ale volání Verifiable
na konci volání nastavení umožňuje ověření napodobení v testu. To se provádí s voláním mockRepo.Verify
, což selže test, pokud očekávaná metoda nebyla volána.
Poznámka
Knihovna Moq použitá v této ukázce umožňuje kombinovat ověřitelné nebo "striktní", napodobení s neověřitelnými napodobením (označovanými také jako "volné" napodobení nebo zástupné procedury). Přečtěte si další informace o přizpůsobení chování napodobení pomocí Moq.
SessionController v ukázkové aplikaci zobrazuje informace související s konkrétní relací debaty. Kontroler obsahuje logiku pro řešení neplatných id
hodnot (v následujícím příkladu existují dva return
scénáře pro pokrytí těchto scénářů). Konečný return
příkaz vrátí nové StormSessionViewModel
zobrazení (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 jednotek zahrnují jeden test pro každý return
scénář v akci kontroleru Index
relace:
[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);
}
Přechod na kontroler Návrhy zpřístupňuje funkce jako webové rozhraní API na api/ideas
trase:
ForSession
seznam nápadů (IdeaDTO
) přidružených k relaci debaty.Create
přidá do relace nové nápady.[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);
}
Vyhněte se vracení entit obchodní domény přímo prostřednictvím volání rozhraní API. Entity domény:
Mapování mezi entitami domény a typy vrácené klientovi je možné provést:
Select
, jak používá ukázková aplikace. Další informace naleznete v tématu LINQ (Language Integrated Query).Dále ukázková aplikace předvádí testy jednotek pro Create
ForSession
metody rozhraní API kontroleru Ideas.
Ukázková aplikace obsahuje dva ForSession
testy. První test určuje, jestli ForSession
vrátí NotFoundObjectResult hodnotu (HTTP Nenalezena) pro neplatnou relaci:
[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);
}
Druhý ForSession
test určuje, jestli ForSession
vrátí seznam nápadů relace (<List<IdeaDTO>>
) pro platnou relaci. Kontroly také prověřují první myšlenku na ověření správnosti jeho Name
vlastnosti:
[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);
}
Pokud chcete otestovat chování Create
metody, když ModelState
je neplatná, ukázková aplikace přidá do kontroleru chybu modelu jako součást testu. Nepokoušejte se testovat ověření modelu nebo vazbu modelu v testech jednotek – pouze otestujte chování metody akce, když je konfrontováno s neplatným ModelState
kódem:
[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);
}
Druhý test Create
závisí na vrácení null
úložiště , takže je nakonfigurované úložiště na vrácení null
. Není potřeba vytvořit testovací databázi (v paměti nebo jinak) a vytvořit dotaz, který vrátí tento výsledek. Test lze provést v jednom příkazu, jak ukazuje ukázkový kód:
[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);
}
Třetí Create
test , ověřuje, Create_ReturnsNewlyCreatedIdeaForSession
že je volána metoda úložiště UpdateAsync
. Napodobení se volá pomocí Verifiable
a metoda napodobeného úložiště Verify
je volána k potvrzení, že je ověřená metoda provedena. Není zodpovědností testu jednotek zajistit, aby UpdateAsync
metoda uložila data – která se dá provést s integračním testem.
[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);
}
ActionResult<T> (ActionResult<TValue>) může vrátit typ odvozený nebo ActionResult
vrátit určitý typ.
Ukázková aplikace obsahuje metodu, která vrací pro danou List<IdeaDTO>
relaci id
. Pokud relace id
neexistuje, vrátí kontroler 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;
}
Dva testy ForSessionActionResult
kontroleru jsou zahrnuty v sadě ApiIdeasControllerTests
.
První test potvrdí, že kontroler vrátí ActionResult
neexistující seznam nápadů pro neexistující relaci id
:
ActionResult
je ActionResult<List<IdeaDTO>>
.[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);
}
Pro platnou relaci id
druhý test potvrdí, že metoda vrátí:
List<IdeaDTO>
TypActionResult
.List<IdeaDTO>
typ.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);
}
Ukázková aplikace obsahuje také metodu vytvoření nové Idea
pro danou relaci. Kontroler vrátí:
[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);
}
Do souboru ApiIdeasControllerTests
jsou zahrnuty tři testy CreateActionResult
.
První text potvrzuje, že BadRequest je vrácen pro neplatný model.
[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);
}
Druhý test zkontroluje, jestli NotFound se vrátí, pokud relace neexistuje.
[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);
}
Pro platnou relaci id
konečný test potvrzuje, že:
ActionResult
BrainstormSession
.CreatedAtActionResult
je podobná odpovědi vytvořené v roce 201 s hlavičkou Location
.BrainstormSession
typ.UpdateAsync(testSession)
bylo vyvoláno. Volání Verifiable
metody je kontrolováno spuštěním mockRepo.Verify()
v kontrolních výrazech.Idea
objekty.Idea
přidaná voláním UpdateAsync
napodobení) odpovídá newIdea
přidané relaci v testu.[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 hrají ústřední roli v libovolné aplikaci ASP.NET Core MVC. Proto byste měli mít jistotu, že se kontrolery chovají tak, jak mají. Automatizované testy můžou detekovat chyby před nasazením aplikace do produkčního prostředí.
Zobrazení nebo stažení ukázkového kódu (postup stažení)
Testy jednotek zahrnují testování části aplikace izolovaně od infrastruktury a závislostí. Pokud je logika kontroleru testování jednotek, testují se pouze obsah jedné akce, nikoli chování jejích závislostí nebo samotné architektury.
Nastavte testy jednotek akcí kontroleru, abyste se mohli zaměřit na chování kontroleru. Test jednotek kontroleru zabraňuje scénářům, jako jsou filtry, směrování a vazby modelu. Testy, které pokrývají interakce mezi komponentami, které souhrnně reagují na požadavek, se zpracovávají integračními testy. Další informace o integračníchtestch ASP.NET ch
Pokud píšete vlastní filtry a trasy, otestujte je izolovaně, ne jako součást testů na konkrétní akci kontroleru.
Pokud chcete předvést testy jednotek kontroleru, projděte si následující kontroler v ukázkové aplikaci. Kontroler Home zobrazí seznam debat a umožňuje vytváření nových debat s požadavkem 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));
}
}
Předchozí kontroler:
IBrainstormSessionRepository
.IBrainstormSessionRepository
architektury objektů, jako je Například Moq. Napodobený objekt je objekt s předem určenou sadou vlastností a chování metod používaných k testování. Další informace naleznete v tématu Úvod do integračních testů.Metoda HTTP GET Index
nemá žádnou smyčku ani větvení a volá pouze jednu metodu. Test jednotek pro tuto akci:
IBrainstormSessionRepository
službu pomocí GetTestSessions
metody. GetTestSessions
vytvoří dvě napodobené debaty s daty a názvy relací.Index
.StormSessionViewModel
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;
}
Metoda Home kontroleru HTTP POST Index
ověřuje, že:
false
, metoda akce vrátí 400 Chybný požadavek ViewResult s příslušnými daty.ModelState.IsValid
je true
: Add
metoda v úložišti.Neplatný stav modelu se testuje přidáním chyb, AddModelError jak je znázorněno v prvním testu níže:
[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();
}
Pokud modelState není platný, vrátí se totéž ViewResult
jako u požadavku GET. Test se nepokouší předat neplatný model. Předání neplatného modelu není platný přístup, protože vazba modelu není spuštěná (i když integrační test používá vazbu modelu). V tomto případě se vazba modelu neotestuje. Tyto testy jednotek testují pouze kód v metodě akce.
Druhý test ověří, že pokud ModelState
je platný:
BrainstormSession
(prostřednictvím úložiště).RedirectToActionResult
hodnotu s očekávanými vlastnostmi.Volání napodobená volání, která nejsou volána, se obvykle ignorují, ale volání Verifiable
na konci volání nastavení umožňuje ověření napodobení v testu. To se provádí s voláním mockRepo.Verify
, což selže test, pokud očekávaná metoda nebyla volána.
Poznámka
Knihovna Moq použitá v této ukázce umožňuje kombinovat ověřitelné nebo "striktní", napodobení s neověřitelnými napodobením (označovanými také jako "volné" napodobení nebo zástupné procedury). Přečtěte si další informace o přizpůsobení chování napodobení pomocí Moq.
SessionController v ukázkové aplikaci zobrazuje informace související s konkrétní relací debaty. Kontroler obsahuje logiku pro řešení neplatných id
hodnot (v následujícím příkladu existují dva return
scénáře pro pokrytí těchto scénářů). Konečný return
příkaz vrátí nové StormSessionViewModel
zobrazení (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 jednotek zahrnují jeden test pro každý return
scénář v akci kontroleru Index
relace:
[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);
}
Přechod na kontroler Návrhy zpřístupňuje funkce jako webové rozhraní API na api/ideas
trase:
ForSession
seznam nápadů (IdeaDTO
) přidružených k relaci debaty.Create
přidá do relace nové nápady.[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);
}
Vyhněte se vracení entit obchodní domény přímo prostřednictvím volání rozhraní API. Entity domény:
Mapování mezi entitami domény a typy vrácené klientovi je možné provést:
Select
, jak používá ukázková aplikace. Další informace naleznete v tématu LINQ (Language Integrated Query).Dále ukázková aplikace předvádí testy jednotek pro Create
ForSession
metody rozhraní API kontroleru Ideas.
Ukázková aplikace obsahuje dva ForSession
testy. První test určuje, jestli ForSession
vrátí NotFoundObjectResult hodnotu (HTTP Nenalezena) pro neplatnou relaci:
[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);
}
Druhý ForSession
test určuje, jestli ForSession
vrátí seznam nápadů relace (<List<IdeaDTO>>
) pro platnou relaci. Kontroly také prověřují první myšlenku na ověření správnosti jeho Name
vlastnosti:
[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);
}
Pokud chcete otestovat chování Create
metody, když ModelState
je neplatná, ukázková aplikace přidá do kontroleru chybu modelu jako součást testu. Nepokoušejte se testovat ověření modelu nebo vazbu modelu v testech jednotek – pouze otestujte chování metody akce, když je konfrontováno s neplatným ModelState
kódem:
[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);
}
Druhý test Create
závisí na vrácení null
úložiště , takže je nakonfigurované úložiště na vrácení null
. Není potřeba vytvořit testovací databázi (v paměti nebo jinak) a vytvořit dotaz, který vrátí tento výsledek. Test lze provést v jednom příkazu, jak ukazuje ukázkový kód:
[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);
}
Třetí Create
test , ověřuje, Create_ReturnsNewlyCreatedIdeaForSession
že je volána metoda úložiště UpdateAsync
. Napodobení se volá pomocí Verifiable
a metoda napodobeného úložiště Verify
je volána k potvrzení, že je ověřená metoda provedena. Není zodpovědností testu jednotek zajistit, aby UpdateAsync
metoda uložila data – která se dá provést s integračním testem.
[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);
}
V ASP.NET Core 2.1 nebo novějším umožňuje ActionResult<T> (ActionResult<TValue>) vrátit typ odvozený nebo ActionResult
vrátit určitý typ.
Ukázková aplikace obsahuje metodu, která vrací pro danou List<IdeaDTO>
relaci id
. Pokud relace id
neexistuje, vrátí kontroler 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;
}
Dva testy ForSessionActionResult
kontroleru jsou zahrnuty v sadě ApiIdeasControllerTests
.
První test potvrdí, že kontroler vrátí ActionResult
neexistující seznam nápadů pro neexistující relaci id
:
ActionResult
je ActionResult<List<IdeaDTO>>
.[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);
}
Pro platnou relaci id
druhý test potvrdí, že metoda vrátí:
List<IdeaDTO>
TypActionResult
.List<IdeaDTO>
typ.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);
}
Ukázková aplikace obsahuje také metodu vytvoření nové Idea
pro danou relaci. Kontroler vrátí:
[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);
}
Do souboru ApiIdeasControllerTests
jsou zahrnuty tři testy CreateActionResult
.
První text potvrzuje, že BadRequest je vrácen pro neplatný model.
[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);
}
Druhý test zkontroluje, jestli NotFound se vrátí, pokud relace neexistuje.
[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);
}
Pro platnou relaci id
konečný test potvrzuje, že:
ActionResult
BrainstormSession
.CreatedAtActionResult
je podobná odpovědi vytvořené v roce 201 s hlavičkou Location
.BrainstormSession
typ.UpdateAsync(testSession)
bylo vyvoláno. Volání Verifiable
metody je kontrolováno spuštěním mockRepo.Verify()
v kontrolních výrazech.Idea
objekty.Idea
přidaná voláním UpdateAsync
napodobení) odpovídá newIdea
přidané relaci v testu.[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);
}
Zpětná vazba k produktu ASP.NET Core
ASP.NET Core je open source projekt. Vyberte odkaz pro poskytnutí zpětné vazby:
Události
Mistrovství Světa v Power BI DataViz
14. 2. 16 - 31. 3. 16
Se 4 šance na vstup, můžete vyhrát konferenční balíček a udělat to na LIVE Grand Finale v Las Vegas
Další informace