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 ?
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