Search code examples
c#asp.netmoqxunit

How to Put method using xUnit


I try to test Update method using xUnit and I don't know how to do it, below is my code:

Put method in controller:

[HttpPut]
[Route("{id}")]
public IActionResult Put([FromBody]BookDto book, [FromRoute]int id)
{
    if (!ModelState.IsValid)
    {
        return BadRequest();
    }
    var isUpdated = _service.Update(book);
    if (!isUpdated)
    {
        return NotFound();
    }
    return Ok();
}

BookService update method:

public bool Update(BookDto book)
{
    var bookDb = _context.Books.FirstOrDefault(x => x.Id == book.Id);
    if(bookDb == null)
    {
        return false;
    }
    bookDb.Title = book.Title;
    _context.SaveChanges();
    var existingAuthors = _context.Book_Authors.Where(x => x.BookId == book.Id).ToList();
    _context.Book_Authors.RemoveRange(existingAuthors);
    _context.SaveChanges();

    foreach (var AuthorsId in book.AuthorsId)
    {
        var newBookAuthors = new Book_Author()
        {
            BookId = book.Id,
            AuthorId = AuthorsId
        };
        _context.Book_Authors.Add(newBookAuthors);
    }
    _context.SaveChanges();

    return true;
}

BookDto:

public class BookDto
{
    public int Id { get; set; }
    public string Title { get; set; }
    public List<int> AuthorsId { get; set; }
}

Any suggestions how to write PutMethod test using Moq?


Solution

    1. I would suggest to collect the test cases:

      • Given an invalid book When I call put Then It returns bad request
      • Given a non-existing book When I call put Then It returns not Found
      • Given an existing book When I call put Then It returns ok
    2. I would suggest to AAA pattern for each test case, for example:

      • Arrange: Setup a book service mock which returns false whenever someone calls the update
      • Act: Call your controller's put method
      • Assert: Verify that the returned result is not found as expected
    3. Try to codify. For example

    public void GivenANonExistingBook_WhenICallPut_ThenItReturnsNotFound()
    {
        //Arrange
        var book = ...;
        var serviceMock = new Mock<IBookService>();
        serviceMock.Setup(svc => svc.Update(It.IsAny<BookDto>()))
                   .Returns(false);
    
        var sut = new YourController(serviceMock.Object);
    
        //Act
        var result = sut.Put(book, 0);
    
        //Assert
        var actionResult = result as NotFoundResult;
        Assert.IsNotNull(actionResult);
    }
    

    It is also a good practice to verify that the Update method of the service has been called only once with the book variable: serviceMock.Verify(svc => svc.Update(book), Times.Once);.