Partial mock test in Durable Function Orchestrator Function - c#

I'm trying to test the behaviour of an Orchestrator function and want to try to only mock calls that depends on external sources. This is to test that the combination of steps aka. the behaviour of the Orchestrator function aqually works.
I'm not interested in seperated tests for every small activity trigger that gets added to the function since the goal is to verify the final result.
So questions are:
Is this even possible?
Can I get the Result in setup and return it as an input in another activity trigger setup?
Current unfinished test method:
[Fact]
public async Task TestOrchestrator()
{
// Setup
var fileContent = Encoding.UTF8.GetBytes("this is some filecontent 😀");
var encodedFileContent = Encoding.Latin1.GetString(fileContent);
const string runId = "changeme";
var loggerMock = new Mock<ILogger>();
var clientMock = new Mock<IDurableOrchestrationContext>();
clientMock
.Setup(context => context.CallActivityAsync<byte[]>(nameof(GetBlobContentAsByteArray), runId))
.ReturnsAsync(fileContent);
// I want EncodeToISO88591 to do it's thing instead of me having to return a fake value.
// clientMock
// .Setup(context => context.CallActivityAsync<string>(nameof(EncodeToISO88591), new DataObject<byte[]>(fileContent)))
// .ReturnsAsync(encodedFileContent);
// I want this to consume the value of EncodeToISO88591, not a fake one.
clientMock
.Setup(context => context.CallActivityAsync<bool>(nameof(UploadToSftp), encodedFileContent))
.ReturnsAsync(true);
//Act
await RunOrchestrator(clientMock.Object, loggerMock.Object);
}
OrchestratorFunction:
public record DataObject<T>(T Data);
const string OrchestratorFunctionName = nameof(RunOrchestrator);
const string EncodeFunctionName = nameof(EncodeToISO88591);
const string GetBlobContentFunctionName = nameof(GetBlobContentAsByteArray);
const string UploadToSftpFunctionName = nameof(UploadToSftp);
[FunctionName(OrchestratorFunctionName)]
public static async Task RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context, ILogger logger)
{
if (!context.IsReplaying)
logger.LogInformation("Starting Orchestration");
var runId = context.GetInput<string>();
byte[] blobContent = await context.CallActivityAsync<byte[]>(GetBlobContentFunctionName, runId);
string encodedContent = await context.CallActivityAsync<string>(EncodeFunctionName, new DataObject<byte[]>(blobContent));
bool sftpUploadSuccess = await context.CallActivityAsync<bool>(UploadToSftpFunctionName, encodedContent);
if (!sftpUploadSuccess)
logger.LogError("Failed to upload to sftp");
if (!context.IsReplaying)
logger.LogInformation($"Completed Orchestration of instance {context.InstanceId}");
}

Related

How to unit test, that a search operation is cancelled (using a cancellation token)?

I am working on a autocomplete component. When typing, a search function is invoked to return results. This search function e.g. calls a api endpoint. Since loading the results may take some time, I want to be able to cancel previous search requests when there are new requests.
Code:
public Func<string, CancellationToken, Task<IEnumerable<T>>> SearchFuncWithCancel { get; set; }
private CancellationTokenSource _cancellationTokenSrc;
private void CancelToken()
{
_cancellationTokenSrc?.Cancel();
_cancellationTokenSrc = new CancellationTokenSource();
}
private async Task OnSearchAsync()
{
IEnumerable<T> searched_items = Array.Empty<T>();
CancelToken();
try
{
searched_items = (await SearchFuncWithCancel(Text, _cancellationTokenSrc.Token)) ?? Array.Empty<T>();
{
catch (Exception e)
{
Console.WriteLine("The search function failed to return results: " + e.Message);
}
//...
}
For every search any previous (and still active) search is cancelled. I would like to unit test this behaviour.
What I've tried so far:
[Test]
public async Task Autocomplete_Should_Cancel_Search_On_Search()
{
// Arrange
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://localhost/autocomplete")
.Respond("application/json", "[\"Foo\", \"Bar\"]");
var client = mockHttp.ToHttpClient();
var comp = Context.RenderComponent<AutocompleteTest1>();
var autocompletecomp = comp.FindComponent<MudAutocomplete<string>>();
autocompletecomp.SetParam(p => p.SearchFuncWithCancel, new Func<string, System.Threading.CancellationToken, Task<IEnumerable<string>>>(async (s, cancellationToken) => {
var result = await client.GetAsync("http://localhost/autocomplete", cancellationToken);
var content = await result.Content.ReadAsStringAsync(cancellationToken);
return JsonConvert.DeserializeObject<IEnumerable<string>>(content);
}));
// Test
autocompletecomp.Find("input").Input("Foo");
comp.WaitForAssertion(() => comp.Find("div.mud-popover").ToMarkup().Should().Contain("Foo"));
autocompletecomp.Find("input").Input("Bar");
comp.WaitForAssertion(() => comp.Find("div.mud-popover").ToMarkup().Should().Contain("Bar"));
}
This test passes, however it does not test the cancellation behaviour yet. I am not sure how to do it. Any ideas?
As long as you can mock the API-call to return something else you should be able to use taskCompletionSources to simulate whatever behavior you desire. I'm not familiar with your specific mock framework, but perhaps something like this should work
// Setup the API method for the first call
CancellationToken cancelToken= null;
var tcs1= new TaskCompletionSource<IEnumerable<T>>();
subjectUnderTest.SearchFuncWithCancel = (t, c) => {
cancelToken= c;
return tcs.Task;
};
var call1 = subjectUnderTest.DoSearch("call1");
Assert.IsFalse(cancelToken.IsCancellationRequested);
// Setup the API-method for the second call
var tcs2= new TaskCompletionSource<IEnumerable<T>>();
subjectUnderTest.SearchFuncWithCancel = (t, c) => tcs2.Task;
var call2 = subjectUnderTest.DoSearch("call2");
Assert.IsTrue(cancelToken.IsCancellationRequested);
// Check that the first call handles a cancelled task correctly
tcs1.SetCanceled();
Assert.IsTrue(call1.IsCanceled);
// Second call succeed
tcs2.SetResult(...);
Assert.IsTrue(call2.IsCompletedSuccessfully);
The first call to the API will return a task that never completes. So when the second call occurs we can check that the cancellation token for the first call is cancelled. I hope the pseudocode is possible to translate to your particular mock framework.

how can I set a breakpoint to get the async result in this scenario?

How can I set a breakpoint to get the async result in this scenario?
static void Main(string[] args)
{
Test().GetAwaiter().GetResult();
}
static async Task Test()
{
await GetAllUsers();
}
static async Task GetAllUsers()
{
using (var client = GetHttpClient())
{
var response = client.GetAsync(baseUrl + "api/v1/users");
}
}
If I set a breakpoint at the end of the using statement in GetAllUsers() then the response value is:
Id = 1, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
When I continue to step through the code I'm not clear on how I can get a handle to the final value. Any suggestions on the code I can include to get a handle to the final return value?
You are missing await keyword near GetAsync function call. After fixing that, you will be able to see the result. (BP at end of using)
static async Task GetAllUsers()
{
using (var client = GetHttpClient())
{
var response = await client.GetAsync(baseUrl + "api/v1/users");
}
}

Moq Service in ASP.NET Core

I have some problem with my unit tests. I want to check that my remove method in service is invoke remove method in repository.
My test method:
[Fact]
public async Task Remove_room_async_should_invoke_remove_room_async_on_room_repository()
{
//Arrange
var room = new Room(Guid.NewGuid(), "A-11");
var roomRepositoryMock = new Mock<IRoomRepository>();
var mapperMock = new Mock<IMapper>();
var roomService = new RoomService(roomRepositoryMock.Object, mapperMock.Object);
//Act
await roomService.RemoveAsync(room.RoomId);
//Assert
roomRepositoryMock.Verify(x => x.RemoveAsync(It.IsAny<Room>()), Times.Once());
}
Currently it return me FAIL with exception becouse room with this id doesn't exist.
My repository remove method implementation:
public async Task RemoveAsync(Room room)
{
_rooms.Remove(room);
await Task.CompletedTask;
}
And service
public async Task RemoveAsync(Guid roomId)
{
var room = await _roomRepository.GetOrFailAsync(roomId);
await _roomRepository.RemoveAsync(room);
}
You need to set up the GetOrFailAsync method on your mock IRoomRepository, otherwise the mock is not going to return anything.
So your arrange section becomes:
var room = new Room(Guid.NewGuid(), "A-11");
var roomRepositoryMock = new Mock<IRoomRepository>();
roomRepositoryMock.Setup(r => r.GetOrFailAsync).Returns(room);
var mapperMock = new Mock<IMapper>();
var roomService = new RoomService(roomRepositoryMock.Object, mapperMock.Object);
You also probably want to verify this call as well:
roomRepositoryMock.Verify(r => r.GetOrFailAsync(room.Id), Times.Once());
And consider changing your other verification to match exactly the room you want to delete, rather than IsAny:
roomRepositoryMock.Verify(x => x.RemoveAsync(room), Times.Once());

Testing an async method in ASP MVC Controller with Nunit

I have an ASP.NET MVC application, with an Controller that features asynchronous methods, returning Task<PartialViewResult> object and marked with the async keyword.
This method only takes the data from the database in async mode.
public async Task<PartialViewResult> SomeMethod()
{
using (var unitOfWork = _factory.Create())
{
var result = await unitOfWork.SomeRepository.GetAsync();
return PartialView(result);
};
}
During testing, the stream just freeze in this spot (At run time this code works well):
var models = await unitOfWork.SomeRepository.GetAsync();
This is my test for this method:
public void GetExchange_GetView_OkModelIsViewModel()
{
//fake Repository returns fake Data from DB
var mockSomeRepository = new Mock<ISomeRepository>();
mockSomeRepository.Setup(x => x.GetAsync(...).Returns(new Task<List<SomeType>>(() => new List<SomeType>()));
//fake UoW returns fake Repository
var mockUnitOfWork = new Mock<IUnitOfWork>();
mockUnitOfWork.Setup(x => x.SomeRepository).Returns(mockSomeRepository.Object);
//fake factory create fake UoW
var fakeUnitOfWorkFactory = new Mock<UnitOfWorkFactory>();
fakeUnitOfWorkFactory.Setup(x => x.Create()).Returns(mockUnitOfWork.Object);
//Our controller
var controller = new SomeController(fakeUnitOfWorkFactory);
//Our async method
var result = controller.SomeMethod();
result.Wait();
//---Assert--
}
Question: why is the stream in my method freezes during test execution???
UPDATE
This test begins to work if I replace
var result = await unitOfWork.SomeRepository.GetAsync();
to
var models = unitOfWork.SomeRepository.GetAsync();
models.Start();
models.Wait();
var result = models.Result;
But I don't quite understand why it works like that. Can someone explain?
When testing an async method your test method should be async as well. NUnit can handle this without issue.
[Test]
public async Task GetExchange_GetView_OkModelIsViewModel() {
// ...
var controller = new SomeController(fakeUnitOfWorkFactory);
var result = await controller.SomeMethod(); // call using await
// ...
}
why is the stream in my method freezes during test execution?
There are a few issue with the test.
The initial example was mixing a blocking call (.Wait()) with async calls which lead to a deadlock, hence the hang (deadlock).
The Test should be converted to be async all the way. The test runner should be able to handle that without any problems.
public async Task GetExchange_GetView_OkModelIsViewModel() { ... }
Next the setup of the GetAsync method is not done correctly.
Because the method was not configured to return a completed task that would allow the code to continue, that would also cause a block on that task
//Arrange
var fakeData = new List<SomeType>() { new SomeType() };
//fake Repository returns fake Data from DB
var mockSomeRepository = new Mock<ISomeRepository>();
mockSomeRepository
.Setup(x => x.GetAsync())
.Returns(Task.FromResult(fakeData)); // <-- note the correction here
Based on what is needed for the test the setup can be simplified even further to
//Arrange
var fakeData = new List<SomeType>() { new SomeType() };
//fake UoF returns fake Data from DB
var mockUnitOfWork = new Mock<IUnitOfWork>();
mockUnitOfWork
.Setup(x => x.SomeRepository.GetAsync()) //<-- note the full call here
.ReturnsAsync(fakeData); //<-- and also the use of the ReturnsAsync here
The wrong object was also being based to the controller. Pass the object of the mock.
//Our controller
var controller = new SomeController(fakeUnitOfWorkFactory.Object);
Exercising of the method under test should then be awaited.
//Our async method
var result = await controller.SomeMethod() as PartialViewResult;
and assertions can be done on the result to verify behavior.
Essentially the problems arose out of how the test was arranged and acted upon. Not the code being tested.
Here is the refactored test
public async Task GetExchange_GetView_OkModelIsViewModel() {
//Arrange
var fakeData = new List<SomeType>() { new SomeType() };
//fake UoF returns fake Data from DB
var mockUnitOfWork = new Mock<IUnitOfWork>();
mockUnitOfWork
.Setup(x => x.SomeRepository.GetAsync())
.ReturnsAsync(fakeData);
//fake factory create fake UoF
var fakeUnitOfWorkFactory = new Mock<UnitOfWorkFactory>();
fakeUnitOfWorkFactory.Setup(x => x.Create()).Returns(mockUnitOfWork.Object);
//Our controller
var controller = new SomeController(fakeUnitOfWorkFactory.Object);
//Act
//Our async method
var result = await controller.SomeMethod() as PartialViewResult;
//---Assert--
result.Should().NotBeNull();
result.Model.Should().NotBeNull();
CollectionAssert.AreEquivalent(fakeData, result.Model as ICollection);
}

BackgroundTask doesn't fire

My background task registers but never fires. I have tried to delete the whole project to erase all tasks, changed the name on the TaskBuilder class, and used different conditions. But nothing seems to work. I sometimes get an error that says it can't show me the error.
Here do I build it:
public async void RegisterBackgroundTask()
{
var taskRegistered = false;
var TaskName = "TimeTriggeredTask";
foreach (var task in BackgroundTaskRegistration.AllTasks)
{
if (task.Value.Name == TaskName)
{
taskRegistered = true;
break;
}
}
var tommorowMidnight = DateTime.Today.AddDays(1);
var timeTilMidnight = tommorowMidnight - DateTime.Now;
var minutesTilMidnight = (uint)timeTilMidnight.TotalMinutes;
if (!taskRegistered)
{
var task = RegisterBackgroundTask("TaskBuilderClass",
"TimeTriggeredTask",
new TimeTrigger(minutesTilMidnight, false),
new SystemCondition(SystemConditionType.InternetAvailable));
await task;
CheckPremieres();
}
}
Builder method:
public static async Task<BackgroundTaskRegistration> RegisterBackgroundTask(String taskEntryPoint, String name, IBackgroundTrigger trigger, IBackgroundCondition condition)
{
await BackgroundExecutionManager.RequestAccessAsync();
var builder = new BackgroundTaskBuilder();
builder.Name = name;
builder.TaskEntryPoint = taskEntryPoint;
builder.SetTrigger(trigger);
builder.AddCondition(condition);
BackgroundTaskRegistration task = builder.Register();
//
// Remove previous completion status from local settings.
//
var settings = ApplicationData.Current.LocalSettings;
settings.Values.Remove(name);
return task;
}
This is the task builder class which I also added to the manifest:
public sealed class TaskBuilderClass : IBackgroundTask
{
//
// The Run method is the entry point of a background task.
//
public void Run(IBackgroundTaskInstance taskInstance)
{
//
// Query BackgroundWorkCost
// Guidance: If BackgroundWorkCost is high, then perform only the minimum amount
// of work in the background task and return immediately.
//
var cost = BackgroundWorkCost.CurrentBackgroundWorkCost;
var settings = ApplicationData.Current.LocalSettings;
settings.Values["BackgroundWorkCost"] = cost.ToString();
App.nHandler.CheckPremieres();
}
}
I'm pretty sure the task needs to be in its own Windows Runtime Component; I've always done it like this. The Microsoft sample on GitHub also has the tasks in a separate project.
Try doing that. And don't forget to reference the newly created prject from your application. That's the thing that I most always forget.
Once you do that, I also suggest you first trigger the task from Visual Studio Debug Location toolbar, just to make sure everything is configured correctly. It should appear in the dropdown and should work correctly from here, otherwise it won't work when scheduled either.

Categories