How do I unit test Service Bus SendAsync method properly? - c#

When running the unit test for Azure Service Bus method SendAsync I get the following exception on the line _queueClient.Verify():
Expected invocation on the mock at least once, but was never performed: x => x.SendAsync({MessageId:})
This is my unit test:
public CustomerSendMsg(){ } //ctor
public async Task ShouldSendMessage()
{
var _queueClient = new Mock<IQueueClient>();
var _sut = new Publisher(_queueClient.Object);
var customer = new Customer()
{
FirstName = "John",
LastName = "Doe"
};
_queueClient.Setup(t => t.SendAsync(It.IsAny<Message>())).Returns(Task.CompletedTask).Verifiable();
await _sut.SendMessageAsync(customer);
var messageBody = JsonSerializer.Serialize(customer);
var msg = new Message(Encoding.UTF8.GetBytes(messageBody));
_queueClient.Verify(t = > t.SendAsync(msg));
}
This is SendMessageAsync method from the Publisher class:
public async Task SendMessageAsync<T>(T obj)
{
var messageBody = JsonSerializer.Serialize(obj);
var msg = new Message(Encoding.UTF8.GetBytes(messageBody));
await _queueClient.SendAsync(msg);
}
Do you have any idea how can I make this unit test work?

Instead of creating a new message object which wouldn't work because the references are different - you could decode the body property from the Message class and verify the customer object's properties.

Instead of doing an object comparison (which is what Moq is doing), you can do the verification that the object being passed to the method has specific properties of the object using It.Is<T>.
_queueClient.Verify(t = > t.SendAsync(It.Is<byte[]>(x => x.Body == Encoding.UTF8.GetBytes(messageBody)));

Related

How to filter by an expression when mocking a repository with Moq

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
...
}

How to mock DbSet using xUnit and NSubstitute?

I started using xUnit and NSubstitute for my unit tests. I want to mock the following method.
public async Task<DecorationModel> GetDecorationWithId(string userId, string decorationId)
{
var decoration = await _db.Decorations
.Include(d => d.BgImage)
.FirstOrDefaultAsync(d => d.Id == decorationId);
if (decoration == null || decoration.OwnerId != userId)
return null;
return new DecorationModel
{
Id = decoration.Id,
Name = decoration.Name,
// Other stuff
};
}
I attempted it but couldn't get it to work. My current test class is as follows;
public class DecorationServiceTests
{
private readonly DecorationService _subject;
private readonly IAppDbContext _db = Substitute.For<IAppDbContext>();
private readonly DbSet<Decoration> _decorationDbSet = Substitute.For<DbSet<Decoration>, IQueryable<Decoration>>();
public DecorationServiceTests()
{
_subject = new DecorationService(_db);
}
[Fact]
public async Task GetDecorationWithId_ShouldReturnDecoration_WhenExists()
{
// Arrange
var userId = new Guid().ToString();
var decorationId = new Guid().ToString();
var decorations = new List<Decoration>()
{
new Decoration()
{
Id = decorationId,
Name = "",
OwnerId = userId,
}
};
_db.Decorations.Returns(_decorationDbSet);
_decorationDbSet.FirstOrDefaultAsync(t => t.Id == decorationId).Returns(decorations.FirstOrDefault());
// Act
var result = await _subject.GetDecorationWithId(userId, decorationId);
// Assert
Assert.Equal(result.Id, decorations[0].Id);
}
}
However, I get the following error:
"The provider for the source 'IQueryable' doesn't implement 'IAsyncQueryProvider'. Only providers that implement 'IAsyncQueryProvider' can be used for Entity Framework asynchronous operations."
I searched on the web but couldn't find a good reference. How can I solve this?
I think you'll be going through a whole lot of pain and suffering if you are trying to mock DbSet. That comes straight from the docs of EFCore: https://learn.microsoft.com/en-us/ef/core/testing/#unit-testing
Instead, you should be trying to use a real db or in-memory one.
See the testing sample here: https://learn.microsoft.com/en-us/ef/core/testing/testing-sample

Mock repository is returning null

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

How moq File.Delete (IFileSystem) in app MVC Moq

My method which work
[HttpPost]
public async Task<ActionResult> DeleteTeam(int id)
{
Team team = await teamRepository.DeleteTeamAsync(id);
var fileToDeletePath = Path.Combine(Server.MapPath("~/Images/NBAlogoImg/"), team.Path);
if (System.IO.File.Exists(fileToDeletePath))
{
System.IO.File.Delete(fileToDeletePath);
}
if (team != null)
{
TempData["message"] = string.Format("{0} был удален", team.Name);
}
return RedirectToAction("Index", "Player");
}
It's my attempt to make a test, but unsuccessful
[TestMethod]
public async Task CanDeletePlayerAsync()
{
//Arrange
Mock<ITeamRepository> teamsMock = new Mock<ITeamRepository>();
Team team2 = new Team { Id = 2, Name = "Boston" , Path = "CHi.png" };
Team team3 = new Team { Id = 3, Name = "Lakers" };
string fullPath = ("~/Images/NBAlogoImg/");
var serverMock = new Mock<HttpServerUtilityBase>();
serverMock.Setup(x => x.MapPath(fullPath)).Returns(#"s:\work");
var httpContextMock = new Mock<HttpContextBase>();
httpContextMock.Setup(x => x.Server).Returns(serverMock.Object);
var mockFile = new Mock<IFileSystem>();
TeamController controller = new TeamController(teamsMock.Object);
controller.ControllerContext = new ControllerContext(httpContextMock.Object, new RouteData(), controller);
teamsMock.Setup(m => m.DeleteTeamAsync(team2.Id)).Returns(Task.FromResult(team2));
// Act
ActionResult result = await controller.DeleteTeam(team2.Id);
mockFile.Verify(x => x.File.Delete(#"s:\work\file.png"));
//Assert
Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));
}
I add the funcional to delete image from app if I delete the team. It works perfect, but how make a test by Moq I try some attempts by unsuccessfully.
I have the error message
Expected invocation on the mock at least once, but was never performed: x => x.File.Delete("s:\work\file.png")
No setups configured.
No invocations performed.
how it fix? I have downloaded IFileSystem and made a moq but verify have been not work.
One obvious solution would be to wrap your File.Delete Call in Custom Class, which implements a Custom interface, For example,
public interface IFileOperations
{
void Delete(string path);
}
For your System Operations, you can create a wrapper class.
public class SystemFileOperations:IFileOperations
{
public void Delete(string path)
{
File.Delete(path);
}
}
Now you can alter your original code to ensure SystemFileOperations is injected at all places where you would require IFileOperations.Delete.
private IFileOperations _fileOperations;
public ControllerName(IFileOperations operations)
{
_fileOperations = operations;
}
Following line would be then replaced
System.IO.File.Delete(fileToDeletePath);
with
_fileOperations.Delete(fileToDeletePath);
And for mocking , you could
var mock = new Mock<IFileOperations>();
mock.Verify(x=>x.Delete(path),Times.AtLeastOnce());
Please note that in your case, due to usage of File.Exists, you might have to mock that as well following the same pattern if you desire so

How to mock SendGrid

I am trying to write a unit test for a method I wrote which sends out an email with SendGrid.
My method is something like this:
public async Task SendEmail(TemplatedMailMessage emailMessage)
{
if (String.IsNullOrWhiteSpace(emailMessage.Html) || String.IsNullOrWhiteSpace(emailMessage.From.ToString()) || !emailMessage.To.Any())
{
throw new Exception("Html, From or To is empty");
}
try
{
// Send the email
await this.TransportWeb.DeliverAsync(emailMessage.GetSendGridMessage());
}
catch (Exception ex)
{
//do stuff
}
//log success
}
the TransportWeb is a property which is set in my constructor through a parameter so I can create a mock object.
public EmailManager(Web transportWeb = null)
{
this.TransportWeb = transportWeb ?? SetupSendGrid();
}
In my test method I am trying to mock the TransportWeb (of type SendGrid.Web) property:
[TestMethod]
public async Task SendEmail_ValidEmailTemplateAndNoParameters_EmailIsSent()
{
//ARRANGE
var templatedMailmessage = new Mock<TemplatedMailMessage>();
var transportWeb = new Mock<Web>();
transportWeb.SetupAllProperties();
transportWeb.Setup(x => x.DeliverAsync(It.IsAny<ISendGrid>()));
var emailManager = new EmailManager(transportWeb.Object);
//ACT
await emailManager.Send(templatedMailmessage.Object);
//ASSERT
transportWeb.Verify(x => x.DeliverAsync(It.IsAny<ISendGrid>()), Times.Once());
}
However, I get the following error:
Invalid setup on a non-virtual (overridable in VB) member: x =>
x.DeliverAsync
Does anybody have an idea how I can fix this?
Okay I fixed it :)
You should not use the Web class but ITransport interface:
var transport = new Mock<ITransport>();
transport.SetupAllProperties();
transport.Setup(x => x.DeliverAsync(It.IsAny<SendGridMessage>())).ReturnsTask();
var em = new EmailManager(transport.Object);
I also used the extensions methods created by Simon V.

Categories