I am really new to XUnit and I will appreciate some help.
I have a really simple API method that adds a bug comment
[HttpPost("{bugId}/comment")]
public async Task<IActionResult> AddComment(Guid bugId, [FromBody] AddCommentForm form)
{
return await _bugService.AddComment(bugId, form) switch
{
Option<BugViewModel>.Some(var bug) => Ok(bug),
Option<BugViewModel>.None => NotFound(),
_ => throw new InvalidOperationException()
};
}
The BugService method AddComment looks like this
public async Task<Option<BugViewModel>> AddComment(Guid bugId, AddCommentForm form)
{
if (await DbBugs.SingleOrDefaultAsync(x => x.Id == bugId) is { } bug)
{
var bugComment = new BugComment
{
Text = form.Text,
Commenter = form.Commenter,
CommentedAt = DateTime.Now,
Bug = bug
};
await _dbContext.BugComments.AddAsync(bugComment);
await _dbContext.SaveChangesAsync();
return new Option<BugViewModel>.Some(BugViewModel.FromData(bug));
}
return new Option<BugViewModel>.None();
}
I am adding the implementation of BugViewModel and BugCommentViewModel with their static FromData methods
public class BugViewModel
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public List<BugCommentViewModel> Comments { get; set; }
public static BugViewModel FromData(Bug bug)
{
return new BugViewModel
{
Id = bug.Id,
Title = bug.Title,
Description = bug.Description,
Comments = bug.Comments.Select(BugCommentViewModel.FromData).ToList()
};
}
}
public class BugCommentViewModel
{
public Guid Id { get; set; }
public string Text { get; set; }
public string Commenter { get; set; }
public DateTime CommentedAt { get; set; }
public static BugCommentViewModel FromData(BugComment comment)
{
return new BugCommentViewModel
{
Id = comment.Id,
Text = comment.Text,
Commenter = comment.Commenter,
CommentedAt = comment.CommentedAt.ToLocalTime()
};
}
}
The Unit Tests look like this
public class BugContollerTests
{
private class Fixture
{
public IBugService BugService { get; } = Substitute.For<IBugService>();
public BugController GetSut() => new BugController(BugService);
}
private readonly Fixture _fixture = new Fixture();
[Fact]
public async Task Create_ResponseBodyIsCreatedBug()
{
var bug = BugViewModel.FromData(FakeBug.I.Generate());
_fixture.BugService.Create(Arg.Any<CreateBugForm>()).Returns(bug);
var sut = _fixture.GetSut();
var response = Assert.IsAssignableFrom<CreatedAtActionResult>(await sut.Create(new CreateBugForm()));
Assert.Same(bug, response.Value);
}
[Fact]
public async Task AddComment_CommentIsAddedtoViewModel()
{
_fixture.BugService.AddComment(Arg.Any<Guid>(), Arg.Any<AddCommentForm>()).Returns(new Option<BugViewModel>.Some(new BugViewModel()));
var sut = _fixture.GetSut();
var response = Assert.IsAssignableFrom<ObjectResult>(await sut.AddComment(Guid.Empty,new AddCommentForm()));
Assert.Single(((BugViewModel) response.Value).Comments);
}
}
So, the Test method I had as an example was Create_ResponseBodyIsCreatedBug but the question I have is regarding the second test method that I have created AddComment_CommentIsAddedtoViewModel().
What I am trying to do is:
you see that the BugService.AddComment -> it adds the comment with the Bug Guid we have provided and basically we return BugViewModel with that Comment added to the Comments collection.
test: that WHEN I add a BugComment it is returned in the ViewModel response and we have added exactly one Comment to the bug.
this line
var response = Assert.IsAssignableFrom<ObjectResult>(await sut.AddComment(Guid.Empty,new AddCommentForm()));
the response.Value is a BugViewModel with empty properties and the Comment property is empty without my inserted comment.
And on this line I get an exception as
Assert.Single(((BugViewModel) response.Value).Comments);
as the Comments property is null.
Look, at this line
_fixture.BugService.AddComment(Arg.Any<Guid>(), Arg.Any<AddCommentForm>())
.Returns(
new Option<BugViewModel>.Some(new BugViewModel()));
You setup your fake BugService to return an empty BugViewModel, which indeed would have Comments equal to null because you initialize it nowhere except FromData.
Use same approach you did in a first test
_fixture.BugService.AddComment(Arg.Any<Guid>(), Arg.Any<AddCommentForm>())
.Returns(
new Option<BugViewModel>.Some(
BugViewModel.FromData(FakeBug.I.Generate())));
Or introduce a constructor
public BugViewModel(Guid id, string title, string description, List<BugCommentViewModel> comments)
{
Id = id;
Title = title;
Description = description;
Comments = comments;
}
// Comments are not NULL now
public BugViewModel(): this(Guid.Empty, string.Empty, string.Empty, new List<BugCommentViewModel>())
{
}
In addition, I don't see any purpose of unit testing a mocked dependency of BugController .
Assert.Single(((BugViewModel) response.Value).Comments);
Better move this logic to a separate BugServiceTest which would validate the number of comments.
One more suggestion in regards to unit testing is instead of relying on ObjectResult, write better three tests:
[Fact]
public async Task AddComment_CommentIsAddedtoViewModel_Success()
{
_fixture.BugService.AddComment(Arg.Any<Guid>(), Arg.Any<AddCommentForm>())
.Returns(
new Option<BugViewModel>.Some(
BugViewModel.FromData(FakeBug.I.Generate())));
var sut = _fixture.GetSut();
Assert.IsAssignableFrom<OkResult>(
await sut.AddComment(Guid.Empty,new AddCommentForm()));
}
[Fact]
public async Task AddComment_BugNotFound()
{
_fixture.BugService.AddComment(Arg.Any<Guid>(), Arg.Any<AddCommentForm>())
.Returns(Option<BugViewModel>.None);
var sut = _fixture.GetSut();
Assert.IsAssignableFrom<NotFoundResult>(
await sut.AddComment(Guid.Empty,new AddCommentForm()));
}
public async Task AddComment_ThrowsForNonsenseData()
{
// Idk Option is value or reference type, so I use default(T)
_fixture.BugService.AddComment(Arg.Any<Guid>(), Arg.Any<AddCommentForm>())
.Returns(default(Option<BugViewModel>));
var sut = _fixture.GetSut();
await Assert.ThrowsAsync<InvalidOperationException>(
() => sut.AddComment(Guid.Empty, new AddCommentForm()));
}
Tell me if that helps
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With