Unit test the code which is in Task.Run() in C# - c#

I have to write unit test to verify the code which was run by Task.Run(),which is wrapped inside an async action as shown below.Since I am not awaiting for the task to complete I am not able to achieve this as Task.Run() runs separately.
The requirement is that The call this.KeepAlive(accountNumber, token) should not be waited to complete to call the next statement.But if that KeepAlive service call fails or any validation fails then it should be logged.
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
readonly IService service;
readonly IService2 service2;
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger, IService service, IService2 service2)
{
_logger = logger;
this.service = service;
this.service2 = service2;
}
[HttpGet]
public async Task<bool> Testmethod(string accountNumber, string viewName, CancellationToken token)
{
_ = Task.Run(() => this.KeepAlive(accountNumber, token));
var accountStatus = await this.service2.GetValidName(viewName, token);
return accountStatus=="Myname";
}
private async Task KeepAlive(string name, CancellationToken token)
{
if (string.IsNullOrEmpty(name))
{
_logger.LogError("name is empty or null");
return;
}
try
{
var isAlive = await this.service.ChekStatusAsyc(name, token);
if (!isAlive)
{
_logger.LogError("Unable to process the request");
}
}
catch
{
_logger.LogError("Service ChekStatusAsyc Failed");
}
}
}
}
I need to verify below in my unit tests
whether service was called
validation logging happened
Exception logging
The Test which was written as below will not work, since I am not awaiting for task to complete.
[Fact]
public async void KeepAliveAsyncShouldBeCalledErrorShouldBeLoggedIfServiceFails()
{
var mockLogger = new Mock<ILogger<WeatherForecastController>>();
var mockservice = new Mock<service>();
mockservice.Setup(x => x.ChekStatusAsyc(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(false);
var mockservice2 = new Mock<service2>();
var controller = new WeatherForecastController(mockLogger.Object, mockservice.Object, mockservice2.Object);
var result = await controller.Testmethod("account0", "test");
mockservice.Verify(x => x.ChekStatusAsyc(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
}
As I am not awaiting, I can't verify anything which was run by Task.Run().
If I use
await this.KeepAlive(accountNumber, token) then the test case which I wrote will work as expected,but it will wait for the task to complete.which is not as per the requirement.
Any suggestions?

Related

Unit test .net 6 console app using IHostApplicationLifeTime

I have a class (CronClass) that inherits from IHostedService with 2 methods which are StartAsync(CancellationToken) and StopAsync(CancellationToken).
How do you go about unit testing the StartAsync method to verify that the code was executed, using Moq Library?
For example:
public class CronClass: IHostedService
{
private readonly IHostedApplicationLifetime applicationLifetime;
private readonly IService service;
// IHostedApplicationLifetime/IService are injected DI to via the constructor
public Task StartAsync(CancellationToken cancellationToken)
{
applicationLifeTime.ApplicationStarted.Register(() =>
{
Task.Run(async () =>
{
log.LogInformation("Cron Started");
await service.Process();
});
});
}
//...
}
I would start with creating a mock of IHostApplicationLifetime
public class MockHostApplicationLifetime : IHostApplicationLifetime, IDisposable
{
internal readonly CancellationTokenSource _ctsStart = new CancellationTokenSource();
internal readonly CancellationTokenSource _ctsStopped = new CancellationTokenSource();
internal readonly CancellationTokenSource _ctsStopping = new CancellationTokenSource();
public MockHostApplicationLifetime()
{
}
public void Started()
{
_ctsStart.Cancel();
}
CancellationToken IHostApplicationLifetime.ApplicationStarted => _ctsStart.Token;
CancellationToken IHostApplicationLifetime.ApplicationStopping => _ctsStopping.Token;
CancellationToken IHostApplicationLifetime.ApplicationStopped => _ctsStopped.Token;
public void Dispose()
{
_ctsStopped.Cancel();
_ctsStart.Dispose();
_ctsStopped.Dispose();
_ctsStopping.Dispose();
}
public void StopApplication()
{
_ctsStopping.Cancel();
}
}
In your unit test create a mock of IService. Create instance of CronClass and call cronClass.StartAsync. Then start MockHostApplicationLifetime. It will trigger registered callback ApplicationStarted.Register. Then verify that Process() was called.
You are starting the task in Register method, so it can happen that the unit test can finish before the task is created and service.Process is called. In that case I would wait some time before verification.
[Test]
public async Task Test1()
{
var hal = new MockHostApplicationLifetime();
var mockService = new Mock<IService>();
var cronClass = new CronClass(hal, mockService.Object);
await cronClass.StartAsync(CancellationToken.None);
hal.Started();
// maybe not needed, test passed without the delay
//await Task.Delay(500);
mockService.Verify(mock => mock.Process());
}

XUnit Mock not throwing exception like I would expect

I have the following test that uses a mock of a class. When I try to throw an exception it never actually throws the exception. It goes on as if the method is actually being called and I am unsure why.
Here is the test:
[Fact]
public async Task ReadResultSetShouldRetry()
{
// Arrange
_cosmosUtilWrapper.Setup(x => x.ReadCosmosResultSet<CosmosRepositoryTests>(It.IsAny<FeedIterator>(), It.IsAny<ILogger>(), It.IsAny<CancellationToken>()))
.Throws(new Exception("It broke"));
var cosmosReadPolicy = new CosmosReadPolicy();
// Act
await Assert.ThrowsAsync<Exception>(async () => await CosmosRepository.ReadCosmosResultSetWithRetry<CosmosRepositoryTests>(_mockFeedIterator.Object, _logger, cosmosReadPolicy, CancellationToken.None));
// Assert
_cosmosUtilWrapper.Verify(x => x.ReadCosmosResultSet<CosmosRepositoryTests>(_mockFeedIterator.Object, _logger, default));
}
Here is the method that it is calling and I have it wrapped in a retry policy:
public static async Task<List<T>> ReadCosmosResultSetWithRetry<T>(
FeedIterator resultSet,
ILogger logger,
CosmosReadPolicy cosmosReadPolicy,
CancellationToken cancellationToken = default)
where T : class
{
CosmosUtilWrapper utilWrapper = new CosmosUtilWrapper();
var readCosmosResultSet = await cosmosReadPolicy.GetPolicy.ExecuteAsync(async () => await utilWrapper.ReadCosmosResultSet<T>(resultSet, logger, cancellationToken));
return readCosmosResultSet;
}
Here is the CosmosUtilWrapper and below is the actual Cosmos Util class:
public class CosmosUtilWrapper
{
public virtual async Task<List<T>> ReadCosmosResultSet<T>(
FeedIterator resultSet,
ILogger logger,
CancellationToken cancellationToken = default)
where T : class
{
return await CosmosUtil.ReadCosmosResultSet<T>(resultSet, logger, cancellationToken);
}
}
Here is the actual static Util method that is being returned in the above class. Had to go about it this way since this class is a static class and unit testing those are not very fun.
public static async Task<List<T>> ReadCosmosResultSet<T>(
FeedIterator resultSet,
ILogger logger,
CancellationToken cancellationToken = default)
where T : class
{
var foundDocuments = new List<T>();
while (resultSet.HasMoreResults)
{
using ResponseMessage responseMessage = await resultSet.ReadNextAsync(cancellationToken);
if (responseMessage.IsSuccessStatusCode)
{
using StreamReader streamReader = new StreamReader(responseMessage.Content);
using JsonTextReader textReader = new JsonTextReader(streamReader);
foundDocuments.AddRange((await JObject.LoadAsync(textReader, cancellationToken)).GetValue("Documents").ToObject<List<T>>());
}
else
{
throw new Exception($"Unable to read cosmos result set. Status code: {responseMessage.StatusCode}");
}
}
return foundDocuments;
}
Finally, Here is the message I get when running the tests
Message:
Assert.Throws() Failure
Expected: typeof(System.Exception)
Actual: (No exception was thrown)
It is because the mockup object _cosmosUtilWrapper in ReadResultSetShouldRetry() is never used in Task<List<T>> ReadCosmosResultSetWithRetry<T>.
In methode Task<List<T>> ReadCosmosResultSetWithRetry<T> you initialize a new object CosmosUtilWrapper utilWrapper = new CosmosUtilWrapper();. So, this object is not the same as the object above.
You can get the object from the mockup object with the following code: _cosmosUtilWrapper.Object. Pass this object in the function or in the constructor of the class when you remove the static from the methode.
For example:
public static async Task<List<T>> ReadCosmosResultSetWithRetry<T>(
FeedIterator resultSet,
ILogger logger,
CosmosReadPolicy cosmosReadPolicy,
CosmosUtilWrapper utilWrapper,
CancellationToken cancellationToken = default)
where T : class
{
var readCosmosResultSet = await cosmosReadPolicy.GetPolicy.ExecuteAsync(async () => await utilWrapper.ReadCosmosResultSet<T>(resultSet, logger, cancellationToken));
return readCosmosResultSet;
}
For example Test:
[Fact]
public async Task ReadResultSetShouldRetry()
{
// Arrange
_cosmosUtilWrapper.Setup(x => x.ReadCosmosResultSet<CosmosRepositoryTests>(It.IsAny<FeedIterator>(), It.IsAny<ILogger>(), It.IsAny<CancellationToken>()))
.Throws(new Exception("It broke"));
var cosmosReadPolicy = new CosmosReadPolicy();
var utilWrapper = _cosmosUtilWrapper.Object;
// Act
await Assert.ThrowsAsync<Exception>(async () => await CosmosRepository.ReadCosmosResultSetWithRetry<CosmosRepositoryTests>(_mockFeedIterator.Object, _logger, cosmosReadPolicy, utilWrapper, CancellationToken.None));
// Assert
_cosmosUtilWrapper.Verify(x => x.ReadCosmosResultSet<CosmosRepositoryTests>(_mockFeedIterator.Object, _logger, default));
}

How can I create a BackGround service that runs a function every given period of time ? Using C# (asp.net core 3.1.1)

I'm trying to make call to a function every specified interval of time, for that m using Background service, here is what I have done:
Here is the Alerting controller where I have the function:
public class AlertingController : ControllerBase
{
private readonly DatabaseContext _context;
private readonly IMapper _mapper;
public AlertingController(DatabaseContext context, IMapper mapper)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
public AlertingController()
{
}
//function that adds in the DB
public async Task<AlertingResponse> GetAlertingToDB()
{
AlertingResponse dataGet;
using (var httpClient = new HttpClient())
{
using (var response = await httpClient
.GetAsync(MetricApiLink))
{
string apiResponse = await response.Content.ReadAsStringAsync();
dataGet = JsonConvert.DeserializeObject<AlertingResponse>(apiResponse);
}
}
if (dataGet.data.alerts != null || dataGet.data.alerts.Count > 0)
{
foreach (var alert in dataGet.data.alerts)
{
CreateAlertQuery QueryAlert = new CreateAlertQuery();
QueryAlert.Name = alert.labels.alertname;
QueryAlert.Instance = alert.labels.instance;
QueryAlert.Serverity = alert.labels.severity;
QueryAlert.Summary = alert.annotations.summary;
QueryAlert.State = alert.state;
QueryAlert.ActiveAt = alert.activeAt;
var _Alert = _mapper.Map<AlertingDataModel>(QueryAlert);
_context.Alertings.Add(_Alert);
await _context.SaveChangesAsync();
}
}
return null;
}
}
I have tested the method with a HTTPGET request, it works fine, add the alerts into my database:
I have created a scooped service where I called the function GetAlertingToDB():
internal interface IScopedAlertingService
{
Task DoWork(CancellationToken stoppingToken);
}
public class ScopedAlertingService : IScopedAlertingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedAlertingService(ILogger<ScopedAlertingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
AlertingController _AlertingToDB = new AlertingController();
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await _AlertingToDB.GetAlertingToDB();
await Task.Delay(10000, stoppingToken);
}
}
}
I have also created the Class that will consume my service, and will run in the BackGround:
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedAlertingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await Task.CompletedTask;
}
}
I injected the dependencies on the Startup Class and added the hosted service:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedAlertingService, ScopedAlertingService>();
The functions are working just fine untill a call the GetAlertingToDB() function and it doesn't work.
Any help would be great, thanks everyone :)
Personally I would rearrange your solution so that your background service doesn't need to create a Controller. Instead the controller, if you still need it at all, should call into your ScopedAlertingService where the work is performed once. Your background service can simply loop forever, with an await Task.Delay().
public class ScopedAlertingService : IScopedAlertingService
{
public async Task DoWork(CancellationToken stoppingToken)
{
// move contents of your AlertingController.GetAlertingToDB here
}
}
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly IServiceProvider _services;
public ConsumeScopedServiceHostedService(IServiceProvider services)
{
_services = services;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(10000, stoppingToken);
using (var scope = _services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedAlertingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
}
}
Hangfire RecurringJob would be an option for you case. you can check it here https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html.
The benefit of using it is: you have a dashboard to check when the task will be fired and the result of the task.
There are several options for doing this.
Please read the following link from the Microsoft Documentation which has several examples on how to do this in .NET Core and ASP.NET Core:
Worker Service In NET Core
It is called Worker Services.
You basically implement two interfaces: IHostedService, IDisposable
Then you register your service inside you Startup class in your ConfigureServices method like this:
services.AddHostedService<MyCoolWorkerServiceClass>();
For a Complete Example
One last sugestion. The example uses System.Threading.Timer... But I think it is better to use a System.Timers.Timer with AutoReset = false.
The reason is to avoid overlapping runs of your service. Once a run is done then you start your timer again.
But then again it all depends on what you want to achieve.

Is there some way to pass a string to a IHostedService timed background task?

I am running a timed background task to send out emails, and in the email I want to include a generated link.
When I send out other emails via user interactions in the controller, I'm using this little method to generate the link:
public string BuildUrl(string controller, string action, int id)
{
Uri domain = new Uri(Request.GetDisplayUrl());
return domain.Host + (domain.IsDefaultPort ? "" : ":" + domain.Port) +
$#"/{controller}/{action}/{id}";
}
Of course, a background task does not know anything about the Http context, so I would need to replace the domain-part of the link, like this:
public string BuildUrl(string controller, string action, int id)
{
return aStringPassedInFromSomewhere + $#"/{controller}/{action}/{id}";
}
I'm starting the background task in startup.cs ConfigureServices like this:
services.AddHostedService<ProjectTaskNotifications>();
I was thinking to maybe get the domainname from a resource file, but then I might as well just hard code it into the task method.
Is there some way to pass this information dynamically to the background task?
MORE INFO
Here is the entire background task:
internal class ProjectTaskNotifications : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
private readonly IServiceScopeFactory scopeFactory;
private readonly IMapper auto;
public ProjectTaskNotifications(
ILogger<ProjectTaskNotifications> logger,
IServiceScopeFactory scopeFactory,
IMapper mapper)
{
_logger = logger;
this.scopeFactory = scopeFactory;
auto = mapper;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(30));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("Timed Background Service is working.");
// Connect to the database and cycle through all unsent
// notifications, checking if some of them are due to be sent:
using (var scope = scopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<MyDbContext>();
List<ProjectTaskNotification> notifications = db.ProjectTaskNotifications
.Include(t => t.Task)
.ThenInclude(o => o.TaskOwner)
.Include(t => t.Task)
.ThenInclude(p => p.Project)
.ThenInclude(o => o.ProjectOwner)
.Where(s => !s.IsSent).ToList();
foreach (var notification in notifications)
{
if (DateTime.UtcNow > notification.Task.DueDate
.AddMinutes(-notification.TimeBefore.TotalMinutes))
{
SendEmail(notification);
notification.Sent = DateTime.UtcNow;
notification.IsSent = true;
}
}
db.UpdateRange(notifications);
db.SaveChanges();
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
public void SendEmail(ProjectTaskNotification notification)
{ // Trimmed down for brevity
// Key parts
string toAddr = notification.Task.TaskOwner.Email1;
BodyBuilder bodyBuilder = new BodyBuilder
{
HtmlBody = TaskInfo(auto.Map<ProjectTaskViewModel>(notification.Task))
};
return;
}
public string TaskInfo(ProjectTaskViewModel task)
{ // Trimmed down for brevity
return $#"<p>{BuildUrl("ProjectTasks", "Edit", task.Id)}</p>";
}
public string BuildUrl(string controller, string action, int id)
{
// This is where I need the domain name sent in from somewhere:
return "domain:port" + $#"/{controller}/{action}/{id}";
}
}
You can pass in any object to the IHostedService provider via the constructor.
public ProjectTaskNotifications(IUrlPrefixProvider provider)
{
_urlPrefixProvider = urlPrefixProvider
}
private string BuildUrl(<Your args>)
{
var prefix = _urlPrefixProvider.GetPrefix(<args>);
....
}
In startup.cs you can have
services.AddSingleton<IUrlPrefixProvider, MyUrlPrefixProvider>()
services.AddHostedService<ProjectTaskNotifications>();
and let dependency injection take care of the rest.

Callback never comes

I have a WebApi controller which calls a third party API in asynchronous mode.
All works ok and now I want to sort the result in a separate action method.
Now, when I call the API, the callback with the result never happens after running "await client.GetAsycn(...)" in the DAL. What am I missing?
This is my API controller:
// GET api/lookup
[ResponseType(typeof(RestaurantModel))]
public async Task<IHttpActionResult> Get(string outcode)
{
if (string.IsNullOrEmpty(outcode)) throw new ArgumentNullException(nameof(outcode));
var result = await _repository.GetRestaurantsByOutcode(outcode);
return Ok(new RestaurantModel()
{
Result = result
});
}
// GET api/sorted
[System.Web.Http.Route("~/api/sorted")]
public List<Restaurant> GetSorted(string outcode)
{
if (string.IsNullOrEmpty(outcode)) throw new ArgumentNullException(nameof(outcode));
return _repository.GetSortedRestaurantsByOutcode(outcode);
}
This is my repository with a new method to sort the result:
public class RestaurantRepository : IRestaurantRepository
{
private readonly IContext _context;
public RestaurantRepository(IContext context)
{
_context = context;
}
public Task<ApiResult> GetRestaurantsByOutcode(string outcode)
{
return _context.GetRestaurantsByOutcode(outcode);
}
public List<Restaurant> GetSortedRestaurantsByOutcode(string outcode)
{
return _context.GetRestaurantsByOutcode(outcode).Result.Restaurants
.OrderBy(x => x.Name).ToList();
}
}
This is my DAL to call the third party API:
public async Task<ApiResult> GetRestaurantsByOutcode(string outcode)
{
using (var client = new HttpClient())
{
ConfigureHttpClient(client);
var response = await client.GetAsync(
$"restaurants?q={WebUtility.UrlEncode(outcode)}");
return response.IsSuccessStatusCode
? await response.Content.ReadAsAsync<ApiResult>()
: null;
}
}
You have a mix-match of sometimes you use async/await and other times you don't. Async / await (can and does by default) ensures that the call resumes on the calling thread so the context is resulted. You need to allign your code so you make use of the async/await in the whole stack. Otherwise you are creating a deadlock for your self.
[System.Web.Http.Route("~/api/sorted")]
// missing async in signature (not good if you are calling it with await in your controller)
public async Task<List<Restaurant>> GetSorted(string outcode)
{
if (string.IsNullOrEmpty(outcode)) throw new ArgumentNullException(nameof(outcode));
// added await in call
return await _repository.GetSortedRestaurantsByOutcode(outcode);
}
DAL
public class RestaurantRepository : IRestaurantRepository
{
private readonly IContext _context;
public RestaurantRepository(IContext context)
{
_context = context;
}
// added async and await
public async Task<ApiResult> GetRestaurantsByOutcode(string outcode)
{
return await _context.GetRestaurantsByOutcode(outcode);
}
// added async and await
public async Task<List<Restaurant>> GetSortedRestaurantsByOutcode(string outcode)
{
// here you were not using await but then using result even though you were calling into a method marked as async which in turn used an await. this is where you deadlocked but this the fix.
return (await _context.GetRestaurantsByOutcode(outcode)).Restaurants
.OrderBy(x => x.Name).ToList();
}
}

Categories

Resources