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 IBrainstormSessionRepositoryklasy .
  • 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ę IBrainstormSessionRepositoryGetTestSessions 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:

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ć:

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 ModelStateelementem :

[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 nullrepozytorium , 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_ReturnsNewlyCreatedIdeaForSessionczy metoda repozytorium jest wywoływana UpdateAsync . Pozorowana metoda jest wywoływana za pomocą Verifiablemetody , 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 ApiIdeasControllerTestsznajdują się dwa testy ForSessionActionResult kontrolera .

Pierwszy test potwierdza, że kontroler zwraca nieistniejącą ActionResult listę pomysłów na nieistniejącą sesję id:

[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 iddrugi test potwierdza, że metoda zwraca następujące elementy:

  • Element ActionResult z typem List<IdeaDTO> .
  • AkcjaResult <T>. Wartość jest typemList<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:

[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 idkońcowy test potwierdza, że:

  • Metoda zwraca element ActionResult o typie BrainstormSession .
  • AkcjaResult <T>. Wynik to CreatedAtActionResult. CreatedAtActionResult jest analogiczny do odpowiedzi 201 Utworzono z nagłówkiem Location .
  • AkcjaResult <T>. Wartość jest typemBrainstormSession.
  • Wywołanie makiety w celu zaktualizowania sesji , UpdateAsync(testSession)zostało wywołane. Wywołanie Verifiable metody jest sprawdzane przez wykonanie mockRepo.Verify() w asercji.
  • Dla sesji są zwracane dwa Idea obiekty.
  • Ostatni element ( Idea dodany przez wywołanie makiety do UpdateAsyncelementu ) jest zgodny newIdea 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 IBrainstormSessionRepositoryklasy .
  • 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ę IBrainstormSessionRepositoryGetTestSessions 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:

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ć:

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 ModelStateelementem :

[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 nullrepozytorium , 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_ReturnsNewlyCreatedIdeaForSessionczy metoda repozytorium jest wywoływana UpdateAsync . Pozorowana metoda jest wywoływana za pomocą Verifiablemetody , 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 ApiIdeasControllerTestsznajdują się dwa testy ForSessionActionResult kontrolera .

Pierwszy test potwierdza, że kontroler zwraca nieistniejącą ActionResult listę pomysłów na nieistniejącą sesję id:

[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 iddrugi test potwierdza, że metoda zwraca następujące elementy:

  • Element ActionResult z typem List<IdeaDTO> .
  • AkcjaResult <T>. Wartość jest typemList<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:

[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 idkońcowy test potwierdza, że:

  • Metoda zwraca element ActionResult o typie BrainstormSession .
  • AkcjaResult <T>. Wynik to CreatedAtActionResult. CreatedAtActionResult jest analogiczny do odpowiedzi 201 Utworzono z nagłówkiem Location .
  • AkcjaResult <T>. Wartość jest typemBrainstormSession.
  • Wywołanie makiety w celu zaktualizowania sesji , UpdateAsync(testSession)zostało wywołane. Wywołanie Verifiable metody jest sprawdzane przez wykonanie mockRepo.Verify() w asercji.
  • Dla sesji są zwracane dwa Idea obiekty.
  • Ostatni element ( Idea dodany przez wywołanie makiety do UpdateAsyncelementu ) jest zgodny newIdea 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