Skip to content

Latest commit

 

History

History
724 lines (587 loc) · 29.3 KB

08_test.md

File metadata and controls

724 lines (587 loc) · 29.3 KB

Unit test controller logic in ASP.NET Core

목차


단위 테스트는 인프라 및 종속성에서 분리된 앱의 일부를 테스트하는 것을 포함합니다. 컨트롤러 로직을 단위 테스트할 때는 단일 액션의 내용만 테스트하며, 종속성이나 프레임워크 자체의 동작은 테스트하지 않습니다.

컨트롤러 단위 테스트

컨트롤러 동작에 집중하기 위해 컨트롤러 액션의 단위 테스트를 설정합니다. 컨트롤러 단위 테스트는 필터, 라우팅, 모델 바인딩과 같은 시나리오를 피합니다. 요청에 응답하는 구성 요소 간의 상호 작용을 다루는 테스트는 통합 테스트에 의해 처리됩니다. 통합 테스트에 대한 자세한 내용은 Integration tests in ASP.NET Core를 참조하십시오.

맞춤 필터와 라우트를 작성하는 경우, 특정 컨트롤러 액션의 테스트의 일부가 아닌 별도의 테스트에서 단위 테스트하십시오.

컨트롤러 단위 테스트를 시연하기 위해 샘플 앱의 다음 컨트롤러를 검토합니다.

샘플 코드 보기 또는 다운로드 (다운로드 방법)

Home 컨트롤러는 브레인스토밍 세션 목록을 표시하고 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));
    }
}

앞서의 컨트롤러는:

  • 명시적 종속성 원칙을 따릅니다.
  • 종속성 주입(DI)을 통해 IBrainstormSessionRepository 인스턴스를 제공합니다.
  • Moq와 같은 모의 객체 프레임워크를 사용하여 모의 IBrainstormSessionRepository 서비스를 통해 테스트할 수 있습니다. 모의 객체는 테스트를 위해 미리 정의된 속성과 메서드 동작을 가진 가상의 객체입니다. 자세한 내용은 통합 테스트 소개를 참조하십시오.

HTTP GET Index 메서드는 반복 또는 분기 없이 단일 메서드만 호출합니다. 이 액션에 대한 단위 테스트는:

  • GetTestSessions 메서드를 사용하여 IBrainstormSessionRepository 서비스를 모의합니다. GetTestSessions는 날짜와 세션 이름이 있는 두 개의 모의 브레인스토밍 세션을 생성합니다.
  • Index 메서드를 실행합니다.
  • 메서드가 반환한 결과에 대해 다음을 주장합니다:
    • ViewResult가 반환됩니다.
    • ViewDataDictionary.ModelStormSessionViewModel입니다.
    • 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;
}

Home 컨트롤러의 HTTP POST Index 메서드 테스트는 다음을 검증합니다:

  • ModelState.IsValidfalse인 경우, 액션 메서드는 적절한 데이터를 가진 400 Bad Request ViewResult를 반환합니다.
  • ModelState.IsValidtrue인 경우:
    • 저장소에서 Add 메서드가 호출됩니다.
    • 올바른 인수를 가진 RedirectToActionResult가 반환됩니다.

유효하지 않은 모델 상태는 아래 첫 번째 테스트에서 AddModelError를 사용하여 오류를 추가하여 테스트합니다:

[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();
}

ModelState가 유효하지 않은 경우, GET 요청과 동일한 ViewResult가 반환됩니다. 테스트는 유효하지 않은 모델을 전달하려고 시도하지 않습니다. 모델 바인딩이 실행되지 않기 때문에 유효하지 않은 모델을 전달하는 것은 유효한 접근 방식이 아닙니다(통합 테스트에서는 모델 바인딩을 사용하지만). 이 경우, 모델 바인딩은 테스트되지 않습니다. 이 단위 테스트는 액션 메서드의 코드만 테스트합니다.

두 번째 테스트는 ModelState가 유효할 때를 검증합니다:

  • 새로운 BrainstormSession이 추가됩니다(저장소를 통해).
  • 메서드는 예상 속성을 가진 RedirectToActionResult를 반환합니다.

호출되지 않은 모의 호출은 일반적으로 무시되지만, 설정 호출 끝에서 Verifiable을 호출하면 테스트에서 모의 검증을 수행할 수 있습니다. 이는 mockRepo.Verify 호출을 통해 수행되며, 예상 메서드가 호출되지 않은 경우 테스트가 실패합니다.

Note

이 샘플에서 사용된 Moq 라이브러리는 검증 가능한 모의("엄격한" 모의)와 검증할 수 없는 모의("느슨한" 모의 또는 스텁)을 혼합하여 사용할 수 있게 합니다. Moq를 사용한 모의 동작 사용자 정의에 대해 자세히 알아보십시오.

샘플 앱의 SessionController는 특정 브레인스토밍 세션과 관련된 정보를 표시합니다. 컨트롤러에는 유효하지 않은 id 값 처리 로직이 포함되어 있습니다(이러한 시나리오를 다루기 위해 두 개의 return 시나리오가 있습니다). 최종 return 문은 뷰에 새로운 StormSessionViewModel을 반환합니다(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);
    }
}

단위 테스트에는 Session 컨트롤러 Index 액션의 각 return 시나리오에 대한 테스트가 포함되어 있습니다:

[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);
}

Ideas 컨트롤러로 이동하여, 앱은 api/ideas 경로에서 웹 API로 기능을 노출합니다:

  • 브레인스토밍 세션과 관련된 아이디어(IdeaDTO) 목록이 ForSession 메서드에 의해 반환됩니다.
  • Create 메서드는 세션에 새로운 아이디어를 추가합니다.
[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);
}

API 호출을 통해 비즈니스 도메인 엔터티를 직접 반환하지 않도록 합니다. 도메인 엔터티는:

  • 클라이언트가 요구하는 것보다 더 많은 데이터를 포함할 수 있습니다.
  • 앱의 내부 도메인 모델과 공개된 API를 불필요하게 결합할 수 있습니다.

도메인 엔터티와 클라이언트에 반환되는 유형 간의 매핑은 다음과 같이 수행할 수 있습니다:

  • 샘플 앱에서 사용하는 것처럼 LINQ Select로 수동으로 수행합니다. 자세한 내용은 LINQ (Language Integrated Query)를 참조하십시오.
  • AutoMapper와 같은 라이브러리를 사용하여 자동으로 수행합니다.

다음으로, 샘플앱은 Ideas 컨트롤러의 CreateForSession API 메서드에 대한 단위 테스트를 시연합니다.

샘플 앱에는 두 개의 ForSession 테스트가 포함되어 있습니다. 첫 번째 테스트는 유효하지 않은 세션에 대해 ForSessionNotFoundObjectResult (HTTP Not Found)를 반환하는지 확인합니다:

[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);
}

두 번째 ForSession 테스트는 유효한 세션에 대해 ForSession이 세션 아이디어(<List<IdeaDTO>>) 목록을 반환하는지 확인합니다. 또한 첫 번째 아이디어의 Name 속성이 올바른지 확인합니다:

[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);
}

Create 메서드의 동작을 테스트하기 위해 샘플 앱은 테스트의 일부로 컨트롤러에 모델 오류를 추가합니다. 단위 테스트에서 모델 유효성 검사나 모델 바인딩을 테스트하려고 하지 말고, 잘못된 ModelState와 마주쳤을 때 액션 메서드의 동작만 테스트하십시오:

[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);
}

Create의 두 번째 테스트는 저장소가 null을 반환하는 경우에 따라 다르므로, 모의 저장소는 null을 반환하도록 구성됩니다. 이 결과를 반환하는 쿼리를 구성하고 메모리 내 또는 기타 방식으로 테스트 데이터베이스를 생성할 필요가 없습니다. 테스트는 샘플 코드에서 설명한 대로 단일 문장으로 완료될 수 있습니다:

[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);
}

세 번째 Create 테스트, Create_ReturnsNewlyCreatedIdeaForSession은 저장소의 UpdateAsync 메서드가 호출되는지 확인합니다. 모의는 Verifiable로 호출되며, 모의 저장소의 Verify 메서드를 호출하여 검증 가능한 메서드가 실행되었는지 확인합니다. 데이터가 저장되었는지 확인하는 것은 단위 테스트의 책임이 아닙니다. 이는 통합 테스트로 수행할 수 있습니다.

[Fact]
public async Task Create_ReturnsNewlyCreatedIdeaForSession()
{
    // Arrange
    int testSessionId = 123;
    string testName = "test name";
    string testDescription = "test description";
    var testSession = GetTestSession();
    var mockRepo = new Mock<IBrainstormSessionRepository>();
    mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
        .ReturnsAsync(testSession);
    var controller = new IdeasController(mockRepo.Object);

    var newIdea = new NewIdeaModel()
    {
        Description = testDescription,
        Name = testName,
        SessionId = testSessionId
    };
    mockRepo.Setup(repo => repo.UpdateAsync(testSession))
        .Returns(Task.CompletedTask)
        .Verifiable();

    // Act
    var result = await controller.Create(newIdea);

    // Assert
    var okResult = Assert.IsType<OkObjectResult>(result);
    var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
    mockRepo.Verify();
    Assert.Equal(2, returnSession.Ideas.Count());
    Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
    Assert.Equal(testDescription, returnSession.Ideas.LastOrDefault().Description);
}

Test ActionResult<T>

ActionResult<T> (ActionResult<TValue>)는 ActionResult를 상속하는 유형을 반환하거나 특정 유형을 반환할 수 있습니다.

샘플 앱에는 주어진 세션 id에 대한 List<IdeaDTO>를 반환하는 메서드가 포함되어 있습니다. 세션 id가 존재하지 않으면, 컨트롤러는 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;
}

두 개의 ForSessionActionResult 컨트롤러 테스트가 ApiIdeasControllerTests에 포함되어 있습니다.

첫 번째 테스트는 컨트롤러가 ActionResult를 반환하지만 존재하지 않는 세션 id에 대한 아이디어 목록을 반환하지 않는지 확인합니다:

  • ActionResult 유형은 ActionResult<List<IdeaDTO>>입니다.
  • ResultNotFoundObjectResult입니다.
[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);
}

유효한 세션 id에 대해, 두 번째 테스트는 메서드가 다음을 반환하는지 확인합니다:

  • List<IdeaDTO> 유형의 ActionResult.
  • ActionResult<T>.ValueList<IdeaDTO> 유형입니다.
  • 목록의 첫 번째 항목은 모의 세션에 저장된 아이디어와 일치하는 유효한 아이디어입니다(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);
}

샘플 앱에는 주어진 세션에 대해 새로운 Idea를 생성하는 메서드도 포함되어 있습니다. 컨트롤러는 다음을 반환합니다:

  • 유효하지 않은 모델에 대해 BadRequest.
  • 세션이 존재하지 않는 경우 NotFound.
  • 세션이 새로운 아이디어로 업데이트되면 CreatedAtAction.
[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);
}

CreateActionResult의 세 가지 테스트가 ApiIdeasControllerTests에 포함되어 있습니다.

첫 번째 테스트는 유효하지 않은 모델에 대해 BadRequest가 반환되는지 확인합니다.

[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);
}

두 번째 테스트는 세션이 존재하지 않는 경우 NotFound가 반환되는지 확인합니다.

[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);
}

유효한 세션 id에 대해, 최종 테스트는 다음을 확인합니다:

  • 메서드는 BrainstormSession 유형의 ActionResult를 반환합니다.
  • ActionResult<T>.ResultCreatedAtActionResult입니다. CreatedAtActionResultLocation 헤더가 있는 201 Created 응답과 유사합니다.
  • ActionResult<T>.ValueBrainstormSession 유형입니다.
  • 세션을 업데이트하기 위한 모의 호출 UpdateAsync(testSession)이 호출되었습니다. 검증 가능한 메서드 호출은 주장에서 mockRepo.Verify()를 실행하여 확인됩니다.
  • 세션에 대해 두 개의 Idea 객체가 반환됩니다.
  • 마지막 항목(모의 호출을 통해 세션에 추가된 Idea)은 테스트에서 세션에 추가된 newIdea와 일치합니다.
[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);
}

추가 자료


출처

Unit test controller logic in ASP.NET Core