moq for mediator using generics in unit testing - c#

I use cqrs and mediatr in .net core 3 webapi project. I want to unit test controller methods behavior, so when the method is called, mediator does it work as expected. I wrote testing code for one method and it works fine. Many code is omitted for brevity, I only used code which causes the error. So, working test:
var mockScrapedAd = ScrapedAd.CreateMockAd(DateTime.Now); //result of controller method
mediatrMock.Setup(m =>
m.Send(It.IsAny<QueryDetails>(),
It.IsAny<CancellationToken>())).
ReturnsAsync(() => mockScrapedAd); //mock for mediator,
// so when something with QueryDetails type is called, it should return result from above
var ret = await controller.Details(0); //should send QueryDetails via Mediatr
Assert.Equal(mockScrapedAd, ret.Value); //all good?
mediatrMock.Verify(x => x.Send(It.IsAny<QueryDetails>(), //was it called at all?
It.IsAny<CancellationToken>()));
This test works, but I will have to duplicate code for each controller method. The only thing that changes here is type of mediatr request (QueryDetails here) and mock for return.
So I tried to use generic method like here:
async Task TestBase<TK,K>(TK expectedResult,Func<Task<ActionResult<TK>>> callControllerMethod)
{
mediatrMock.Setup(m =>
m.Send(It.IsAny<K>(),
It.IsAny<CancellationToken>())).ReturnsAsync(() => expectedResult);
var ret = await callControllerMethod();
Assert.Equal(expectedResult, ret.Value);
mediatrMock.Verify(x => x.Send(It.IsAny<K>(),
It.IsAny<CancellationToken>()));
}
And the test suite will be easier:
var mockScrapedAd = ScrapedAd.CreateMockAd(DateTime.Now);
await TestBase<ScrapedAd,QueryDetails>(mockScrapedAd, async () => await controller.Details(0));
Basically all I changed is moved QueryDetails inside generic method. Mediator fails to recognize it and response is not called. What's wrong here?

Related

Delay Returns with NSubstitute

I have an interface IDiscosClient, for testing/demo purposes while I'm developing the app, I want a mock to return a new model when the .GetSingle<T>() method is called with a random delay of between 1 and 5 seconds. This is mostly so I can see that all of my various loading spinner components and whatnot work.
So, I thought I'd be able to do something like this:
Fixture fixture = new();
fixture.Customize(new DiscosModelFixtureCustomizationNoLinks());
builder.Services.AddTransient(_ =>
{
IDiscosClient client = Substitute.For<IDiscosClient>();
DiscosObject obj = fixture.Create<DiscosObject>();
client.GetSingle<DiscosObject>(Arg.Any<string>()).Returns(Task.Delay(Random.Shared.Next(1000,5000)).ContinueWith(_ => obj));
return client;
});
However, while there seems to be a delay when I first call the method, once this has resolved, it just seems to return the completed task with the same model in it every time I call it for that IDiscosClient instance.
Is there a simple enough way to accomplish this?
So the issue is that the code above only creates a fresh Task the first time and then returns the same one (which has already completed) each subsequent time.
To fix this, we can either change the code above to:
client.GetSingle<DiscosObject>(Arg.Any<string>()).Returns(_ => Task.Delay(Random.Shared.Next(1000,5000)).ContinueWith(_ => obj));
Or, for legibilities sake, we can extract it into a method and make the whole code block:
builder.Services.AddTransient(_ =>
{
IDiscosClient client = Substitute.For<IDiscosClient>();
client.GetSingle<DiscosObject>(Arg.Any<string>()).Returns(GetDiscosObject);
return client;
});
async Task<DiscosObject> GetDiscosObject(CallInfo _)
{
await Task.Delay(Random.Shared.Next(1000, 5000));
return fixture.Create<DiscosObject>();
}

How to return a Task<string> using Moq?

I am using XUnit and Moq to test code from my logic layer. My logic layer also communicates with the data layer, so I want to mock the interface to keep my test simple.
I am wondering how I should return a Task<string> when I call the async Task method. My GetOrder method calls GetOrderById but the data layer method returns null.
Edit: I changed my unit test based on all the feedback. It works fine now.
My test:
public async void GetOrder()
{
//Arrange
string expected = "test";
var mock = new Mock<IRepository>();
mock.Setup(arg => arg.GetOrderNameById(It.IsAny<int>())
.Returns(Task.FromResult(expected));
var survey = new SurveyResult(mock.Object);
//Act
string result = await survey.GetOrderNameById(It.IsAny<int>()));
//Assert
Assert.Equal(expected, result);
}
Use Task.FromResult(expected)
mock.Setup(arg => arg.GetScoreByTotalWeighting(value)).Returns(Task.FromResult(expected))
also I'd recomend to avoid value as parameter, when you dont care about that paramter when returning result. You can use It.IsAny<int>(), like that:
mock.Setup(arg => arg.GetScoreByTotalWeighting(It.IsAny<int>())).Returns(Task.FromResult(expected))
Problem that you setting up mock.Setup(arg => arg.GetScoreByTotalWeighting(value)) with value == 0 and then call survey.GetResult(score) with 50. Use It.IsAny<int>() at both places to avoid that problem, or pass same value:
mock.Setup(arg => arg.GetScoreByTotalWeighting(score))
Solution:
public async Task GetResult()
{
//Arrange
string expected = "test";
var mock = new Mock<IRetreiveQuestionRepository>();
mock.Setup(arg => arg.GetScoreByTotalWeighting(It.IsAny<int>()))
.ReturnsAsync(expected);
var survey = new SurveyResult(mock.Object);
//Act
string result = await survey.GetResult(It.IsAny<int>());
//Assert
Assert.Equal(expected, result);
}
You should be able to use the .ReturnsAsync method you're currently using, It's hard to tell why your subject is returning null without knowing the implementation. But I would double check the stubbing to see if you're specifying the value that is indeed passed to the real implementation or if you're missing any other stubbing for another method.
Hope that helps :)

Proper way of testing ASP.NET Core IMemoryCache

I'm writing a simple test case that tests that my controller calls the cache before calling my service. I'm using xUnit and Moq for the task.
I'm facing an issue because GetOrCreateAsync<T> is an extension method, and those can't be mocked by the framework. I relied on internal details to figure out I can mock TryGetValue instead and get away with my test (see https://github.com/aspnet/Caching/blob/c432e5827e4505c05ac7ad8ef1e3bc6bf784520b/src/Microsoft.Extensions.Caching.Abstractions/MemoryCacheExtensions.cs#L116)
[Theory, AutoDataMoq]
public async Task GivenPopulatedCacheDoesntCallService(
Mock<IMemoryCache> cache,
SearchRequestViewModel input,
MyViewModel expected)
{
object expectedOut = expected;
cache
.Setup(s => s.TryGetValue(input.Serialized(), out expectedOut))
.Returns(true);
var sut = new MyController(cache.Object, Mock.Of<ISearchService>());
var actual = await sut.Search(input);
Assert.Same(expected, actual);
}
I can't sleep with the fact that I'm peeking into the MemoryCache implementation details and it can change at any point.
For reference, this is the SUT code:
public async Task<MyViewModel> Search(SearchRequestViewModel request)
{
return await cache.GetOrCreateAsync(request.Serialized(), (e) => search.FindAsync(request));
}
Would you recommend testing any differently?
To be honest I would recommend not to test this interaction at all.
I would approach this test case a bit differently: what you really care about is that once your controller retrieved data from your ISearchService it shouldn't request the data again and should return the result from the previous call.
The fact that an IMemoryCache is used behind the scenes is just an implementation detail. I wouldn't even bother setting up a test double for it, I would just use an instance of the Microsoft.Extensions.Caching.Memory.MemoryCache object.
My new test would look something like this:
[Theory]
public async Task GivenResultAlreadyRetrieved_ShouldNotCallServiceAgain()
{
// Arrange
var expected = new MyViewModel();
var cache = new MemoryCache(new MemoryCacheOptions());
var searchService = new Mock<ISearchService>();
var input = new SearchRequestViewModel();
searchService
.SetupSequence(s => s.FindAsync(It.IsAny<SearchRequestViewModel>()))
.Returns(Task.FromResult(expected))
.Returns(Task.FromResult(new MyViewModel()));
var sut = new MyController(cache, searchService.Object);
// Act
var resultFromFirstCall = await sut.Search(input);
var resultFromSecondCall = await sut.Search(input);
// Assert
Assert.Same(expected, resultFromFirstCall);
Assert.Same(expected, resultFromSecondCall);
}

Testing Task-based WCF call

I am converting the WCF calls I am making in an application to run asynchronously to ensure the GUI is responsive while it gets data. Mostly I am using these methods to populate the properties of a ViewModel.
For instance, here's my old and new code:
private async Task LoadDataItems()
{
//DataItems = Service.SelectDataItems();
DataItems = await Service.SelectDataItemsAsync();
}
Also, here's some test code using RhinoMocks:
//Doesn't set DataItems when LoadDataItems() is called
myWcfServiceClient.Stub(async client => await client.SelectDataItemsAsync()).Return(new Task<List<DataItemDto>>(() => new List<DataItemDto> { testDataItem }));
//NullReferenceException on SelectDataItemsAsync()
myWcfServiceClient.Stub(client => client.SelectDataItemsAsync().Result).Return(new List<DataItemDto> { testDataItem });
Basically, in my unit test, either DataItems isn't set or I get a NullReferenceException trying to fake out the result. This is probably as much a question on RhinoMocks than anything...
In RhinoMocks, you define the Result on the Task-based operation with Task.FromResult(...)
So, my test code would set up the result as follows:
myWcfServiceClient.Stub(client => client.SelectDataItemsAsync()).Return(Task.FromResult(new List<DataItemDto> { testDataItem }));
Simple and works great!

Using Moq to mock an asynchronous method for a unit test

I am testing a method for a service that makes a Web API call. Using a normal HttpClient works fine for unit tests if I also run the web service (located in another project in the solution) locally.
However when I check in my changes the build server won't have access to the web service so the tests will fail.
I've devised a way around this for my unit tests by creating an IHttpClient interface and implementing a version that I use in my application. For unit tests, I make a mocked version complete with a mocked asynchronous post method. Here's where I have run into problems. I want to return an OK HttpStatusResult for this particular test. For another similar test I will be returning a bad result.
The test will run but will never complete. It hangs at the await. I am new to asynchronous programming, delegates, and Moq itself and I've been searching SO and google for a while learning new things but I still can't seem to get past this problem.
Here is the method I am trying to test:
public async Task<bool> QueueNotificationAsync(IHttpClient client, Email email)
{
// do stuff
try
{
// The test hangs here, never returning
HttpResponseMessage response = await client.PostAsync(uri, content);
// more logic here
}
// more stuff
}
Here's my unit test method:
[TestMethod]
public async Task QueueNotificationAsync_Completes_With_ValidEmail()
{
Email email = new Email()
{
FromAddress = "bob#example.com",
ToAddress = "bill#example.com",
CCAddress = "brian#example.com",
BCCAddress = "ben#example.com",
Subject = "Hello",
Body = "Hello World."
};
var mockClient = new Mock<IHttpClient>();
mockClient.Setup(c => c.PostAsync(
It.IsAny<Uri>(),
It.IsAny<HttpContent>()
)).Returns(() => new Task<HttpResponseMessage>(() => new HttpResponseMessage(System.Net.HttpStatusCode.OK)));
bool result = await _notificationRequestService.QueueNotificationAsync(mockClient.Object, email);
Assert.IsTrue(result, "Queue failed.");
}
What am I doing wrong?
Thank you for your help.
You're creating a task but never starting it, so it's never completing. However, don't just start the task - instead, change to using Task.FromResult<TResult> which will give you a task which has already completed:
...
.Returns(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)));
Note that you won't be testing the actual asynchrony this way - if you want to do that, you need to do a bit more work to create a Task<T> that you can control in a more fine-grained manner... but that's something for another day.
You might also want to consider using a fake for IHttpClient rather than mocking everything - it really depends on how often you need it.
Recommend #Stuart Grassie's answer above.
var moqCredentialMananger = new Mock<ICredentialManager>();
moqCredentialMananger
.Setup(x => x.GetCredentialsAsync(It.IsAny<string>()))
.ReturnsAsync(new Credentials() { .. .. .. });
With Mock.Of<...>(...) for async method you can use Task.FromResult(...):
var client = Mock.Of<IHttpClient>(c =>
c.PostAsync(It.IsAny<Uri>(), It.IsAny<HttpContent>()) == Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))
);
Try using ReturnsAsync.
In asynchronous methods it works, I believe the basis to solve your problem should be similar.
_mocker.GetMock<IMyRepository>()
.Setup(x => x.GetAll())
.ReturnsAsync(_myFakeListRepository.GetAll());

Categories