I am writing a simple xunit test case for a service class.
I have mocked _userAPIRepository but it returns null value. It should return teacherDetails.
Below is the service class method and xunit test case method.
Service method
public async Task<List<TeacherDto>> GetTeacherDetails(Teacher teacher)
{
List<TeacherDto> teacherDetails = new List<TeacherDto>();
UserByUsername userByUsername = new UserByUsername
{
Username = teacher.Name,
Id = teacher.Id
};
teacherDetails = await _userServiceAPI.GetUserIdsByName(userByUsername);
return teacherDetails;
}
xunit test case
[Fact()]
public async Task GetUserDetails()
{
//Arrange
List<TeacherDto> teacherDetails = ServiceDataSource.teacherDetails();
Teacher teacher = ServiceDataSource.teacher();
UserByUsername userByUsername = new UserByUsername
{
Username = teacher.Name,
Id = teacher.Id
};
_userAPIRepository.Setup(x => x.GetUserIdsByName(userByUsername)).Returns(Task.FromResult(teacherDetails);
var result = await _userServiceAPI.GetTeacherDetails(teacher);
//Assert
Assert.True(result.Count > 0);
}
The mock returns null by default when the arguments setup do not match what was passed in when exercising the test. In the presented case, an actual instance/reference was used which does not match the instance created within the subject under test.
Use argument matcher to get the desired behavior from mock since the argument is initialized within the subject under test.
[Fact()]
public async Task GetUserDetails() {
//Arrange
List<TeacherDto> teacherDetails = ServiceDataSource.teacherDetails();
Teacher teacher = ServiceDataSource.teacher();
_userAPIRepository
.Setup(x => x.GetUserIdsByName(It.Is<UserByUsername>(u =>
u.Username == teacher.Name && u.Id == teacher.Id)))
.ReturnsAsync(teacherDetails);
//Act
var result = await _userServiceAPI.GetTeacherDetails(teacher);
//Assert
Assert.True(result.Count > 0);
}
Note the use of It.Is<> to add an argument matcher with a specific filter/predicate.
If there is no need to actually match the members and just accept any UserByUsername, then It.IsAny<> could be used instead
For example
[Fact()]
public async Task GetUserDetails() {
//Arrange
List<TeacherDto> teacherDetails = ServiceDataSource.teacherDetails();
Teacher teacher = ServiceDataSource.teacher();
_userAPIRepository
.Setup(x => x.GetUserIdsByName(It.IsAny<UserByUsername>()))
.ReturnsAsync(teacherDetails);
//Act
var result = await _userServiceAPI.GetTeacherDetails(teacher);
//Assert
Assert.True(result.Count > 0);
}
Reference Moq Quickstart to get a better understanding of how to use that library
Related
I have this class called Handler, which is a MassTransit IConsumer:
public class Handler : IConsumer<ICommand>
{
private readonly IOrderRepository _orderRepository;
public Handler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
}
public async Task Consume(ConsumeContext<ICommand> context)
{
var command = context.Message;
var orderId = new OrderId(command.OrderId);
var order = await _orderRepository.FindOrderAsync(orderId, context.CancellationToken);
if (order is null)
{
await context.RespondAsync(CommandResponse.NotFound);
return;
}
order.Cancel();
await _orderRepository.SaveOrderAsync(order, context.CancellationToken);
await context.RespondAsync(CommandResponse.Submitted);
}
}
I have two unit tests for it. Here's the one that seems to work fine:
[TestMethod]
public async Task Consume_WithExistingOrderId_CancelsOrderAndSavesChangesAndReturnsSubmitted()
{
// Arrange
var mockConsumer = new Mock<IConsumer<ICommand>>();
var mockRepository = new Mock<IOrderRepository>();
var sut = new Handler(mockRepository.Object);
var mockCommand = new Mock<ICommand>();
var mockContext = new Mock<ConsumeContext<ICommand>>();
mockContext.Setup(x => x.Message).Returns(mockCommand.Object);
mockContext.Setup(x => x.RespondAsync(It.IsAny<CommandResponse>())).Returns(Task.CompletedTask);
var existingOrderId = new OrderId(Guid.NewGuid());
mockCommand.Setup(x => x.OrderId).Returns(existingOrderId.Value);
var order = GetTestOrder(existingOrderId);
mockRepository.Setup(x => x.FindOrderAsync(existingOrderId, It.IsAny<CancellationToken>())).ReturnsAsync(order);
// Act
await sut.Consume(mockContext.Object);
// Assert
mockRepository.Verify(x => x.SaveOrderAsync(order, It.IsAny<CancellationToken>()), Times.Once());
mockContext.Verify(x => x.RespondAsync(CommandResponse.Submitted), Times.Once());
order.IsCancelled.Should().BeTrue();
}
And here's the one that isn't doing what I expected:
[TestMethod()]
public async Task Consume_WithNonExistantOrderId_ReturnsNotFoundResponseAndDoesNotSave()
{
// Arrange
var mockRepository = new Mock<IOrderRepository>();
var sut = new Handler(mockRepository.Object);
var mockCommand = new Mock<ICommand>();
var mockContext = new Mock<ConsumeContext<ICommand>>();
mockContext.Setup(x => x.Message).Returns(mockCommand.Object);
mockContext.Setup(x => x.RespondAsync(It.IsAny<CommandResponse>())).Returns(Task.CompletedTask);
var nonExistantOrderId = new OrderId(Guid.NewGuid());
mockCommand.Setup(x => x.OrderId).Returns(nonExistantOrderId.Value);
mockRepository.Setup(x => x.FindOrderAsync(nonExistantOrderId, It.IsAny<CancellationToken>())).ReturnsAsync((Order?)null);
// Act
await sut.Consume(mockContext.Object);
// Assert
mockRepository.Verify(x => x.SaveOrderAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()), Times.Never());
mockContext.Verify(x => x.RespondAsync(CommandResponse.NotFound), Times.Once());
}
Both unit tests require that the Handler calls the RespondAsync method of the MassTransit context exactly once. However, the second unit test doesn't pass, saying that the method was never called. I don't see why it was never called. When I debug into the method it appears to show the method is called.
I can't tell if my test is wrong or if my system under test is wrong. Can anybody see the problem please?
(Also, if anybody can see how to make my code more testable and my unit tests shorter and simpler that would also be appreciated.)
The problem is with the nonExistantOrderId and using that for the match in expectation.
mockRepository
.Setup(x => x.FindOrderAsync(nonExistantOrderId, It.IsAny<CancellationToken>()))
.ReturnsAsync((Order?)null);
the mock expects to get that specific instance when the subject is being exercised but the subject initialized its own instance which causes the mock to not invoke the async call and exit the subject before that target line can be invoked.
This is why
mockRepository.Verify(x => x.SaveOrderAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()), Times.Never());
supposedly passed verification, and
mockContext.Verify(x => x.RespondAsync(CommandResponse.NotFound), Times.Once());
failed since the subject code exited before reaching both members that are the targets of your verification.
Loosen the match using It.IsAny<OrderId>()
mockRepository
.Setup(x => x.FindOrderAsync(It.IsAny<OrderId>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Order?)null);
so that the mocked async call can be invoked and allow the code to flow to completion.
I have accepted Nkosi's answer because it solved the problem stated in the question. Thank you, Nkosi.
However, the resources that were provided by the MassTransit boss-man, #Chris Patterson, were helpful in making a better unit test altogether. That's why I'm posting this alternative answer. This answer rewrites the original unit test to use the MassTransit test harness, and I hope it helps somebody one day.
Chris's links have somehow disappeared from sight. However, I think he posted this:
https://www.youtube.com/watch?v=Cx-Mc0DCpfE&t=545s
And this:
https://masstransit-project.com/usage/testing.html
The rewritten method:
public interface ICommand
{
Guid OrderId { get; }
}
public record Command : ICommand
{
public Command(Guid orderId)
{
OrderId = orderId;
}
public Guid OrderId { get; }
}
public class Handler : IConsumer<ICommand>
{
private readonly IOrderRepository _orderRepository;
public Handler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
}
public async Task Consume(ConsumeContext<ICommand> context)
{
var command = context.Message;
OrderId orderId = new OrderId(command.OrderId);
var order = await _orderRepository.FindOrderAsync(orderId, context.CancellationToken);
if (order is null)
{
await context.RespondAsync(CommandResponse.NotFound);
return;
}
order.Cancel();
await _orderRepository.SaveOrderAsync(order, context.CancellationToken);
await context.RespondAsync(CommandResponse.Submitted);
}
}
The rewritten unit test class:
[TestClass()]
public class Handler_Tests
{
private static OrderId ExistingOrderId = new OrderId("d94108e4-1121-401a-a6ef-c7736054041d");
private static OrderId NonExistentOrderId = new OrderId("daaa72a0-8f8c-4a9b-b4ad-ebefbb6b5aa2");
private static CustomerId ExistingCustomerId = new CustomerId("5fbf40d8-c064-4821-8948-a520863e6242");
[TestMethod()]
public async Task Consume_WithExistingOrderId_CancelsOrderAndSavesChangesAndReturnsSubmitted2()
{
// Arrange
var harness = new InMemoryTestHarness();
var mockRepository = GetMockOrderRepository();
var sut = harness.Consumer(() =>
{
return new Handler(mockRepository.Object);
});
await harness.Start();
try
{
var requestClient = await harness.ConnectRequestClient<ICommand>();
var command = new Command(ExistingOrderId.Value);
// Act
var response = await requestClient.GetResponse<CommandResponse>(command, It.IsAny<CancellationToken>());
// Assert
mockRepository.Verify(x => x.SaveOrderAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()), Times.Once());
response.Message.Result.Should().Be(CommandResponse.Results.Submitted);
}
finally
{
await harness.Stop();
}
}
[TestMethod()]
public async Task Consume_WithNonExistentOrderId_ReturnsNotFoundResponseAndDoesNotSave()
{
// Arrange
var harness = new InMemoryTestHarness();
var mockRepository = GetMockOrderRepository();
var sut = harness.Consumer(() =>
{
return new Handler(mockRepository.Object);
});
await harness.Start();
try
{
var requestClient = await harness.ConnectRequestClient<ICommand>();
var command = new Command(NonExistentOrderId.Value);
var response = await requestClient.GetResponse<CommandResponse>(command, It.IsAny<CancellationToken>());
mockRepository.Verify(x => x.SaveOrderAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()), Times.Never());
response.Message.Result.Should().Be(CommandResponse.Results.NotFound);
}
finally
{
await harness.Stop();
}
}
private static Order GetTestOrder(OrderId orderId)
{
var orderItem = GetTestOrderItem();
return Order.Place(orderId, ExistingCustomerId, new[] { orderItem });
}
private static OrderItem GetTestOrderItem()
{
var productId = new ProductId(Guid.NewGuid());
var price = new Price(1, PurchaseCurrency.Usd);
var quantity = new Quantity(1, UnitOfMeasure.Each);
return new OrderItem(productId, quantity, price);
}
private static Mock<IOrderRepository> GetMockOrderRepository()
{
var mockRepository = new Mock<IOrderRepository>();
mockRepository.Setup(x => x.FindOrderAsync(It.IsAny<OrderId>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new InvalidOperationException(
$"This mock is set up to work with {nameof(ExistingOrderId)} and {nameof(NonExistentOrderId)}."));
mockRepository.Setup(x => x.FindOrderAsync(ExistingOrderId, It.IsAny<CancellationToken>()))
.ReturnsAsync(GetTestOrder(ExistingOrderId));
mockRepository.Setup(x => x.FindOrderAsync(NonExistentOrderId, It.IsAny<CancellationToken>()))
.ReturnsAsync((Order?)null);
return mockRepository;
}
}
In my test project i am using xUnit with Moq,
in my controller test class i also need an object of type Mock<UserManager<ApplicationUser>> to get the current user informations.
That's why i am using Moq to Setup the async method FindByIdAsync the setup is configured like this :
public async Task EntrsAdd_ShouldReturnSuccessJsonResult_WhenModelIdIsNull()
{
//Arrange
var id = _fixture.Create<string>();
ApplicationUser applicationUserMock = _fixture.Create<ApplicationUser>();
var viewModelMock = _fixture.Create<AddOrUpdateEntrViewModel>();
viewModelMock.Id = null;
_userManager.Setup(u => u.FindByIdAsync(id))
.Returns(Task.FromResult<ApplicationUser>(applicationUserMock));
//Act
var result = await _sutEntrsController.Add(viewModelMock).ConfigureAwait(false);
int test = 0;
//Assert
}
the method i am testing in the controller is:
public async Task<IActionResult> Add(AddOrUpdateEntrViewModel model)
{
var user = await _userManager.FindByIdAsync(model.UsrId);
var entr = _mapper.Map<Entr>(model);
entr.Usr = $"{user.FirstName} {user.LastName}";
if (model.Id is null)
{
await _entrService.AddAsync(entr);
}
else
{
await _entrService.UpdateAsync(entr);
}
return new JsonResult("success.");
}
The problem is that the setup uses the id variable and the corresponding property in the viewmodel is not set.
Either set the correct id for UsrId on the ViewModelMock or relax the setup by using It.IsAny<string>():
var viewModelMock = _fixture.Create<AddOrUpdateEntrViewModel>();
viewModelMock.Id = null;
viewModelMock.UsrId = id;
or
_userManager.Setup(u => u.FindByIdAsync(It.IsAny<string>()))
.Returns(Task.FromResult<ApplicationUser>(applicationUserMock));
I have a method in a repository that receives an Expression as a parameter and the result of the method are supposed to filter by that expression. The thing is that when I try to test this method, it doesn't give me the result filtered:
Service method I'm testing
public async Task<Response<IEnumerable<AirlineDto>>> GetAllAsync()
{
try
{
var airlines = await _airlineRepository.GetAsync(x => x.Status); // call to the real repo
... // some logic
}
... // exception handling
}
Method Setup, MockAirlineRepository is a mock of AirlineRepository
public MockAirlineRepository MockGetAll()
{
Setup(x => x.GetAsync(It.IsAny<Expression<Func<Airline, bool>>>()))
.ReturnsAsync(GetTestAirlines);
return this;
}
private static IEnumerable<Airline> GetTestAirlines()
{
var airlines = new List<Airline>
{
new()
{
Id = Guid.NewGuid(),
Name = "Test One",
Status = true
},
new()
{
Id = Guid.NewGuid(),
Name = "Test Two",
Status = true
},
new()
{
Id = Guid.NewGuid(),
Name = "Test Three",
Status = false // notice that this Airline Status is false, so,
// the count of the retrieved values should be 2
}
};
return airlines;
}
Test
[Fact]
public void AirlineService_GetAll_ReturnsAirlines()
{
//Arrange
var mockAirlineRepo = new MockAirlineRepository().MockGetAll();
var airlineService = new AirlineService(mockAirlineRepo.Object, _airlineMapper);
//Act
var result = airlineService.GetAllAsync().Result.Data;
//Assert
var airlineDtoList = result.ToList();
Assert.NotEmpty(airlineDtoList);
Assert.Equal(2, airlineDtoList.Count); // assert fails, because airlineDtoList.Count is 3
mockAirlineRepo.VerifyGetAllAirlines(Times.Once());
}
Let's start with the basics:
Dummy: simple code that returns bogus data
Fake: a working alternative which can take shortcuts
Stub: custom logic with predefined data
Mock: custom logic with expectations (interactive stub)
Shim: custom logic at run-time (replace static with a delegate)
Spy: interceptors to record calls
So, your mock is an interactive stub which means it can return different outputs based on the received input. In your Setup call you have made the following statement: whatever I receive as a filter expression I should always return with the entire list
If you want to apply the filter then change your setup
.Setup(x => x.GetAsync(It.IsAny<Expression<Func<Airline, bool>>>()))
.ReturnsAsync(filter => GetTestAirlines.Where(filter));
Now if you assert on airlineDtoList.Count then it will return 2. But in this case you are not testing the GetAllAsync function rather than your mock.
So, I would rather spend more time testing the // some logic
You can also take advantage of async-await in your test
[Fact]
public Task AirlineService_GetAll_ReturnsAirlines()
{
//Arrange
var mockAirlineRepo = new MockAirlineRepository().MockGetAll();
var airlineService = new AirlineService(mockAirlineRepo.Object, _airlineMapper);
//Act
var response = await airlineService.GetAllAsync();
var result = response.Data;
//Assert
...
}
My controller for the delete method :
[HttpDelete("{toDoListId}")]
public async Task<ActionResult> DeleteToDoList(int toDoListId)
{
var toDoListEntity = await _toDoListRepository.GetSpecificTodoAsync(toDoListId);
if (toDoListEntity == null)
{
return NotFound();
}
_toDoListRepository.DeleteToDoList(toDoListEntity);
await _toDoListRepository.SaveChangesAsync();
return NoContent();
}
My repository :
public async Task<ToDoList?> GetSpecificTodoAsync(int taskId)
{
return await _context.ToDoLists.Where(c => c.Id == taskId).FirstOrDefaultAsync();
}
public void DeleteToDoList(ToDoList toDoListDto)
{
_context.ToDoLists.Remove(toDoListDto);
}
My testcase for checking if the item got deleted and if it returns no content after being deleted. But both of my test cases are failing. Any help on how to write test cases for the delete part, I would be really grateful. I am also trying to test other methods but I am unfortunately stuck here. Please kindly help me
public class UnitTest1
{
private readonly Mock<IToDoListRepository> repositoryStub = new ();
private readonly Mock<IMapper> mapper = new Mock<IMapper>();
private readonly Random Rand = new();
private ToDoList GenerateRandomItem()
{
return new()
{
Id = Rand.Next(),
Description= Guid.NewGuid().ToString(),
Title = Guid.NewGuid().ToString(),
StartDate = DateTime.Now,
EndDate = DateTime.Now,
Done = false
};
}
[Fact]
public void Delete_removesEntry()
{
//arrange
var existingItem = GenerateRandomItem();
var controller = new ToDoController(repositoryStub.Object, mapper.Object);
var itemID = existingItem.Id;
//act
controller.DeleteToDoList(itemID);
//assert
Assert.Null(repositoryStub.Object.GetSpecificTodoAsync(itemID));
}
[Fact]
public async Task DeleteItemAsync_WithExistingItem_ReturnNoContent()
{
//Arrange
var existingItem = GenerateRandomItem();
repositoryStub.Setup(repo => repo.GetSpecificTodoAsync(existingItem.Id)).ReturnsAsync((existingItem));
var itemID = existingItem.Id;
var controller = new ToDoController(repositoryStub.Object, mapper.Object);
//Act
var result = await controller.DeleteToDoList(itemID);
//assert
result.Should().BeOfType<NoContentResult>();
}
Both test cases are failing because the mock has not been setup to behave as expected for each test case.
There is also a potential race condition in the shown tests since they are sharing the same mock instance. This will cause issues when setting up the mock as one test case could potentially override the setup of another case.
Update the tests so that they are isolated from each other.
In the first test, the expected behavior can be verified by checking the mock to see if the expected member was invoked.
[Fact]
public async Task DeleteToDoList_Should_RemoveEntry() {
//arrange
ToDoList existingItem = GenerateRandomItem();
var itemID = existingItem.Id;
Mock<IToDoListRepository> repositoryStub = new ();
//Setup expected behavior of mock
repositoryStub
.Setup(_ => _.GetSpecificTodoAsync(itemID))
.ReturnsAsync(existingItem);
var controller = new ToDoController(repositoryStub.Object, mapper.Object);
//act
await controller.DeleteToDoList(itemID);
//assert
repositoryStub.Verify(_ => _.DeleteToDoList(existingItem));
}
In the other test, the mock needs be setup to make sure the subject executes to completion.
[Fact]
public async Task DeleteToDoList_WithExistingItem_Should_ReturnNoContent() {
//Arrange
ToDoList existingItem = GenerateRandomItem();
var itemID = existingItem.Id;
Mock<IToDoListRepository> repositoryStub = new ();
//Setup expected behavior of mock
repositoryStub
.Setup(_ => _.GetSpecificTodoAsync(itemID))
.ReturnsAsync(existingItem);
repositoryStub.Setup(_ => _.SaveChangesAsync()).ReturnsAsynt(true);
var controller = new ToDoController(repositoryStub.Object, mapper.Object);
//Act
ActionResult result = await controller.DeleteToDoList(itemID);
//assert
result.Should().BeOfType<NoContentResult>();
}
I'm new to testing with Moq, so I'm confused a bit regarding ReturnsAsync.
Here is my test method, where I'm expecting ReturnsAsync should receive type that will be returned by method defined in Setup. Am I right?
But seemed ReturnsAsync should have the other signature - it's looking for Func delegate.
[TestClass]
public class TourObjectControllerTest
{
[TestMethod]
public async Task GetTourObjects()
{
var mockService = new Mock<ITourObjectService>(MockBehavior.Default);
mockService.Setup(x => x.GetAll()).ReturnsAsync(new Task<IEnumerable<TourObjectDTO>>);
var controller = new TourObjectController(mockService.Object);
var result = await controller.Get();
Assert.IsNotNull(result);
Assert.AreSame(typeof(TourObjectViewModel), result);
}
}
Method under test:
public async Task<IEnumerable<TourObjectViewModel>> Get()
{
IEnumerable<TourObjectViewModel> viewmodel = null;
try
{
var tourobjects = await _tos.GetAll();
viewmodel = Mapper.Map<IEnumerable<TourObjectViewModel>>(tourobjects);
}
catch (Exception ex)
{
Log.ErrorFormat("Method:{0} <br/> Error: {1}", System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
}
return viewmodel;
}
Pass an actual result.
Assuming ITourObjectService.GetAll() returns Task<IEnumerable<TourObjectDTO>>
eg
[TestClass]
public class TourObjectControllerTest
{
[TestMethod]
public async Task GetTourObjects()
{
var fakeData = new List<TourObjectDTO>() {
new TourObjectDTO { ... }
};
var mockService = new Mock<ITourObjectService>(MockBehavior.Default);
mockService.Setup(x => x.GetAll()).ReturnsAsync(fakeData);
var controller = new TourObjectController(mockService.Object);
var result = await controller.Get();
Assert.IsNotNull(result);
Assert.IsTry(typeof(IEnumerable<TourObjectViewModel>).IsAssignableFrom(result.GetType());
}
}
ReturnsAsync() should take a return value as the paramter instead of Task<TReurnValue>. For example, next 2 lines of code are equal (by behavior):
mockService.Setup(x => x.GetAll()).ReturnsAsync(new List<TourObjectDTO>());
mockService.Setup(x => x.GetAll()).Returns(Task.FromResult(new List<TourObjectDTO>()));
You need to replace new List<TourObjectDTO>() with your test data which you want to retrieve in the test from the mock. For example, you can create just few test values:
var testData = new [] { new TourObjectDTO(1, "test1"), new TourObjectDTO(2, "test2") };
mockService.Setup(x => x.GetAll()).ReturnsAsync(testData);
or create fake data generator if needed.
Note that ReturnsAsync() is only available for methods that return a Task<T>. For methods that return only a Task, .Returns(Task.FromResult(default(object))) can be used.