Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XUnit test for the response body content

Tags:

c#

xunit.net

I am really new to XUnit and I will appreciate some help.

Controller

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

Service

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

ViewModels

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

Testing

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.

like image 673
Tania Marinova Avatar asked Nov 15 '25 11:11

Tania Marinova


1 Answers

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

like image 196
Andriy Shevchenko Avatar answered Nov 17 '25 08:11

Andriy Shevchenko



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!