Search code examples
unit-testing.net-coreasp.net-core-webapixunitmediator

test method returns Null Exception using XUnit


We are using CQRS/Mediator patterns, I'm trying to write some unit tests for the APIs, I started by the controllers.

this is the method CreateAync() that I want to write unit test for.

[HttpPost]
    [MustHavePermission(FSHAction.Create, FSHResource.Article)]
    [OpenApiOperation("Create new Article.", "Content State => Draft = 0 , Published = 1")]
    public async Task<ArticleDto> CreateAsync(CancellationToken cancellationToken, CreateArticleCommand request)
    {
        var article = await Mediator.Send(request, cancellationToken);

        article.CreatedByUser = await _userService.GetAsync(article.CreatedBy.ToString(), cancellationToken);

        article.LastModifiedByUser = await _userService.GetAsync(article.CreatedBy.ToString(),   cancellationToken);

        return article;
    }

this is the CreateCommand class :

public class CreateArticleCommand : IRequest<ArticleDto>
{
    public CreateArticleCommand(string title, string content, Guid contentCategoryId, ContentItemState contentState, List<ContentTagDto> contentTags)
    {
        Title = title;
        Content = content;
        ContentCategoryId = contentCategoryId;
        ContentState = contentState;
        ContentTags = contentTags;
    }

    public string Title { get; set; } = default!;
    public string Content { get; set; } = default!;
    public Guid ContentCategoryId { get; set; }
    public ContentItemState ContentState { get; set; }
    public List<ContentTagDto> ContentTags { get; set; }

}

this is my test method :

[Fact]
    public async Task CreateAsync_ReturnsValidArticleDto()
    {
        // Arrange
        var cancellationToken = CancellationToken.None;
        var controllerContext = new ControllerContext
        {
            HttpContext = _httpContext.Object
        };
        _articlesController.ControllerContext = controllerContext;
        var contentTag = new ContentTagDto(1, "test")
        {
            Id = 1,
            Title = "test"
        };

        var contentTagDtoList = new List<ContentTagDto>() { contentTag };
        var createArticleCommand = new CreateArticleCommand("Title", "content", Guid.NewGuid(), ContentItemState.Draft, contentTagDtoList);

        var mediatorMock = new Mock<IMediator>(); // Replace with your actual IMediator interface
        var userServiceMock = new Mock<IUserService>(); // Replace with your actual IUserService interface

        // Set up the Mediator mock to return a dummy ArticleDto
        mediatorMock
            .Setup(m => m.Send(createArticleCommand, cancellationToken))
            .ReturnsAsync(new ArticleDto { Id = Guid.NewGuid() });

        // Set up the UserService mock to return a dummy user
        var user = new UserDto { Id = Guid.NewGuid(), Email = "test@test.com", FirstName = "Test", LastName = "User" };
        _userService
            .Setup(x => x.GetAsync(_currentUserMock.Object.GetUserId().ToString(), CancellationToken.None))
            .ReturnsAsync(user);

        // Act
        var result = await _articlesController.CreateAsync(cancellationToken, createArticleCommand);

        // Assert
        Assert.NotNull(result);
    }

I get Null Exception, after debuggin I discovered that the exception is from CreatAsync() method in the controller, looks like the article is always null, how can I solve this "Article" null exception ?


Solution

  • The solution was mocking Sender instead of Mediator.

    First it's mandatory to inject the ISender interface in the constructor, then use it in the test method.

    private readonly Mock<IUserService> _userService;
    private readonly ArticlesController _articlesController;
    private readonly Mock<ISender> _sender;
    private readonly Mock<HttpContext> _httpContext;
    
    
    public ArticlesControllerTests()
    {
        _sender = new Mock<ISender>();
        _httpContext = new Mock<HttpContext>();
        
       _httpContext.Setup(ctx=>ctx.RequestServices.GetService(typeof(ISender)))
               .Returns(_sender.Object);
        _currentUserMock = new Mock<ICurrentUser>();
    
        _articlesController = new ArticlesController(_userService.Object);
        var controllerContext = new ControllerContext
        {
            HttpContext = _httpContext.Object
        };
        _articlesController.ControllerContext = controllerContext;
    }
    

    it's also important to HttpContext and then initiate the controllerContext.

    for the test method I used _sender instead of _mediator :

     //// Arrange
        var tesId = Guid.NewGuid();
    
        // -Create a test request
        var testRequest = new CreateArticleCommand("Test Title", "Test Content", Guid.NewGuid(), ContentItemState.Draft, new List<ContentTagDto>());
    
        // -Create a test article
        var testArticle = new ArticleDto
        {
            Id = Guid.NewGuid(),
            Title = testRequest.Title,
            Content = testRequest.Content,
            ContentCategoryId = testRequest.ContentCategoryId,
            ContentState = testRequest.ContentState,
            ContentTags = testRequest.ContentTags,
            CreatedBy = tesId,
            LastModifiedBy = tesId,
            CreatedByUser = new UserDto { Id = tesId, Email = "test@test.com", 
       FirstName = "Test", LastName = "User" }
        };
    
        _sender.Setup(m => m.Send(
            It.IsAny<CreateArticleCommand>(),
            It.IsAny<CancellationToken>())).ReturnsAsync(testArticle);
    
        //// Act
        var result = await _articlesController.CreateAsync(new 
        CancellationToken(), testRequest);
    
        //// Assert
        Assert.NotNull(result);
    

    Now this test method works fine and I'm pretty happy that I could solve this problem by myserlf :D