I have the following scenario:
in my Test class:
public class ControllerTests
{
private readonly MyClass aux;
private readonly Mock<IOrchestrator> _orchestrator;
public ControllerTests()
{
orchestrator = new();
aux = new MyClass(_orchestrator.Object);
}
[Fact]
public async Task MyTest()
{
var result = await aux.Start(new MyParameters());
result.Should().BeOfType<AcceptedResult>();
}
}
MyClass:
public class MyClass : ControllerBase
{
private readonly Orchestrator _orchestrator;
[HttpPost]
public async Task<IActionResult> Start([FromBody] MyParametera parameters)
{
var result = await _orchestrator.StartProcessAsync(parameters);
return Accepted(result);
}
}
and at the end the Orcherstrator class:
public class Orchestrator : IOrchestrator{
public async Task<Guid> StartProcessAsync(MyParameters parameters)
{
var guid = await MyLogic(parameters, async () => {
var manager = _SomeLogic.Get<SomeClass>();
await manager.StartAsync(parameters);
});
}
}
while debugging the test I notice I am not able to hit a breakpoint in the Orchestrator class at the StartProcessAsync method. But I am awaiting everything! What am I doing wrong? Thank you!
Related
I have a multi tenant web API where I seed a database with initial data.
I also have a transient IUserService which has a GetCustomerId function to retrieve the current customerId. This service is used in the databaseContext to store the CustomerId foreign key on the created domain entity "under the hood".
So when I seed the database I create a new scope and use a ICurrentUserInitializer to set the CustomerId in the IUserService for that scope, so the CustomerId is valid when the database context stores the entity.
This works just fine in development, but not for testing. Since I want to mock the IUserService when I test, this means that Moq overrides the GetCustomerId. But I only want to mock that service AFTER I've finished seeding the test database.
I've also tried not mocking the IUserService, and instead use a ICurrentUserInitializer for every test that runs, i.e. for every test, create a new scope, set the CustomerId with the ICurrentUserInitializer in that scope, and run the test in that scope, and then reset for the next test. This seems to work, but isn't as flexible when you want to run tests as different users and it doesn't seem as elegant, since I have to write more code to handle the scope correctly.
I Use xUnit, Moq, Respawn, and Microsoft.AspNetCore.Mvc.Testing
DbContext :
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
int? customerId = CurrentUser.GetCustomerId();
HandleAuditingBeforeSaveChanges(customerId);
int result = await base.SaveChangesAsync(cancellationToken);
return result;
}
private void HandleAuditingBeforeSaveChanges(int? customerId)
{
foreach (var entry in ChangeTracker.Entries<IMustHaveTenant>().ToList())
{
entry.Entity.CustomerId = entry.State switch
{
EntityState.Added => customerId.Value,
_ => entry.Entity.CustomerId
};
}
}
DatabaseInitializer :
public async Task InitializeApplicationDbForCustomerAsync(Customer Customer, CancellationToken cancellationToken)
{
// First create a new scope
using var scope = _serviceProvider.CreateScope();
// This service injects a CustomerId, so that ICurrentUser retrieves this value, but
// doesn't work, since Moq overrides the value
scope.ServiceProvider.GetRequiredService<ICurrentUserInitializer>()
.SetCurrentCustomerId(customer.Id);
// Then run the initialization in the new scope
await scope.ServiceProvider.GetRequiredService<ApplicationDbSeeder>()
.SeedDatabaseAsync(_dbContext, cancellationToken);
}
CustomWebApplicationFactory:
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration(configurationBuilder =>
{
var integrationConfig = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
configurationBuilder.AddConfiguration(integrationConfig);
});
builder.ConfigureServices((context, services) =>
{
services
.Remove<DbContextOptions<ApplicationDbContext>>()
.AddDbContext<ApplicationDbContext>((sp, options) =>
{
options.UseSqlServer(context.Configuration.GetConnectionString("DefaultConnection"),
builder => builder.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName));
});
});
builder.ConfigureTestServices(services =>
{
services
.Remove<ICurrentUser>()
.AddTransient(_ => Mock.Of<ICurrentUser>(s =>
s.GetCustomerId() == GetCurrentCustomerId()));
});
}
}
Testing / CollectionFixture :
public class DatabaseCollection : ICollectionFixture<Testing>
{
}
public partial class Testing : IAsyncLifetime
{
private static WebApplicationFactory<Program> _factory = null!;
private static IConfiguration _configuration = null!;
private static IServiceScopeFactory _scopeFactory = null!;
private static Checkpoint _checkpoint = null!;
private static int? _currentCustomerId = null;
public Task InitializeAsync()
{
_factory = new CustomWebApplicationFactory();
_scopeFactory = _factory.Services.GetRequiredService<IServiceScopeFactory>();
_configuration = _factory.Services.GetRequiredService<IConfiguration>();
_checkpoint = new Checkpoint
{
TablesToIgnore = new[] { new Table("__EFMigrationsHistory") },
};
return Task.CompletedTask;
}
public static int? GetCurrentCustomerId()
{
return _currentCustomerId;
}
public static void RunAsDefaultUserAsync()
{
_currentCustomerId = DefaultValues.Customer.Id;
}
public static async Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
{
using var scope = _scopeFactory.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<ISender>();
return await mediator.Send(request);
}
public static async Task<TEntity?> FindAsync<TEntity>(params object[] keyValues)
where TEntity : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
return await context.FindAsync<TEntity>(keyValues);
}
public static async Task AddAsync<TEntity>(TEntity entity)
where TEntity : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
context.Add(entity);
await context.SaveChangesAsync();
}
public static async Task ResetState()
{
await _checkpoint.Reset(_configuration.GetConnectionString("DefaultConnection"));
await _factory.Services.InitializeDatabasesAsync();
_currentCustomerId = null;
}
public Task DisposeAsync()
{
return Task.CompletedTask;
}
}
DepartmentTest:
[Collection("Database collection")]
public class GetDepartmentsTest : BaseTestFixture
{
[Fact]
public async Task ShouldReturnDepartments()
{
RunAsDefaultUserAsync();
var query = new ListDepartmentRequest();
var result = await SendAsync(query);
result.ShouldNotBeNull();
}
[Fact]
public async Task ShouldReturnAllDepartments()
{
RunAsDefaultUserAsync();
await AddAsync(new Department
{
Description = "Department 1",
});
await AddAsync(new Department
{
Description = "Department 2",
});
var query = new ListDepartmentRequest();
var result = await SendAsync(query);
result.ShouldNotBeNull();
result.Count.ShouldBe(2);
}
}
BaseTestFixture:
public class BaseTestFixture : IAsyncLifetime
{
public async Task InitializeAsync()
{
await ResetState();
}
public async Task DisposeAsync()
{
await ResetState();
//return Task.CompletedTask;
}
}
I created a unit test for an azure function which is below (azure funtion)
public class Processor
{
private readonly static TimeSpan _TimeSpan = new TimeSpan(0, 4, 30);
private readonly IHandler<Request> _Classifier;
public Processor(IHandler<Request> Classifier)
{
_Classifier = Classifier;
}
[FunctionName("Processor")]
public async Task Run([TimerTrigger("0 * * * * *", RunOnStartup = false)] TimerInfo myTimer)
{
await _Classifier.ProcessCall(new string[] { "AR" }, 100, _TimeSpan);
}
}
And in unit test checking a repository function is called or not.
public class ProcessorTests
{
private readonly Mock<IRepository> _RepositoryMock = new Mock<IRepository>();
private readonly Mock<IHandler<Request>> _Classifier;
public ProcessorTests()
{
_Classifier = new Mock<IHandler<Request>>();
}
private Processor GetSetup()
{
return new Processor(_Classifier.Object);
}
[Fact]
public async Task Getcall_check()
{
GivenCode();
var sut = GetSetup();
await sut.Run(null);
_RepositoryMock.Verify(x => x.GetCodeFinder(It.IsAny<IList<string>>(), It.IsAny<DateTimeOffset>(), It.IsAny<string>(), It.IsAny<int>()), Times.Once);
}
private void GivenCode()
{
_RepositoryMock
.Setup(m => m.GetCodeFinder(It.IsAny<IList<string>>() ,It.IsAny<DateTimeOffset>(), It.IsAny<string>(), It.IsAny<int>()))
.ReturnsAsync(new Response<Call>(false, null, Enumerable.Empty<CallOut>()));
}
}
unit test is failed . when I debug the issue, this function _Classifier.ProcessCall does not get called. I put some breakpoints inside the _Classifier.ProcessCall but it's not received there. GetCodeFinder function is actually inside the ProcessCall..
I don`t understand why it not called ? any help
public interface IHandler<TCode>
{
Task ProcessCallouts(IEnumerable<string> countryCodes, int batchSize, TimeSpan safeShutDownTimeSpan);
}
public class Handler<TCode> : IHandler<TCode>
{
private readonly IRepository _Repository;
private readonly IServiceV2<Code, TCode> _Service;
public Handler(
IRepository repository,
IServiceV2<Code, TCode> Service,
)
{
_Repository = repository;
_Service = Service;
}
public async Task Process(IEnumerable<string> countryCodes, int batchSize, TimeSpan safeShutDownTimeSpan)
{
var Response = await _Repository.GetCodeFinder(code, time, pageToken, batchSize);
}
I create the BackgroundService:
public class CustomService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
//...
}
}
and I added to the project:
public class Startup
{
//...
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<CustomService>();
//...
}
//...
}
How can I find the CustomService from another class?
How to start it again?
Create an interface just for the call to StartAsync:
public interface ICustomServiceStarter
{
Task StartAsync(CancellationToken token = default);
}
public class CustomService : BackgroundService, ICustomServiceStarter
{
//...
Task ICustomServiceStarter.StartAsync(CancellationToken token = default) => base.StartAsync(token);
//...
}
Register the interface as a singleton:
public class Startup
{
//...
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSingleton<ICustomServiceStarter, CustomService>();
}
//...
}
and inject ICustomServiceStarter when needed:
public class MyServiceControllerr : Controller
{
ICustomServiceStarter _starter;
public MyServiceController(ICustomServiceStarter starter)
{
_starter = starter;
}
[HttpPost]
public async Task<IActionResult> Start()
{
await _starter.StartAsync();
return Ok();
}
}
When it comes to controller's action, using "await BackgroundService.StartAsync" is the wrong way for long-running tasks.
For instance, the main ussue could be request's timeout depended on proxy settings.
Here is an example how to make your BackgroundService restartable.
BackgroundService implementation:
public class MyBackgroundService: BackgroundService
{
private volatile bool _isFinished = false;
private SemaphoreSlim _semaphore = new SemaphoreSlim(0,1);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_isFinished = false;
// DoSomeWork
_isFinished = true;
await WaitToRestartTask(stoppingToken);
}
private async Task WaitToRestartTask(CancellationToken stoppingToken)
{
// wait until _semaphore.Release()
await _semaphore.WaitAsync(stoppingToken);
// run again
await base.StartAsync(stoppingToken);
}
public void RestartTask()
{
if (!_isFinished)
throw new InvalidOperationException("Background service is still working");
// enter from _semaphore.WaitAsync
_semaphore.Release();
}
}
Controller's action (for instance):
public async Task<IActionResult> Restart()
{
var myBackgroundService= _serviceProvider.GetServices<IHostedService>()
.OfType<MyBackgroundService>()
.First();
try
{
myBackgroundService.RestartTask();
return Ok($"MyBackgroundService was restarted");
}
catch (InvalidOperationException exception)
{
return BadRequest(exception.Message);
}
}
public class ArticlesController : Controller
{
private readonly IArticleRepository _articleRepository;
public ArticlesController(IArticleRepository articleRepository)
{
_articleRepository = articleRepository;
}
public async Task<IActionResult> Post([FromBody]ArticleModel model)
{
var article = create article object by fill input from model ;
await _articleRepository.InsertAsync(article);
return Created($"articles/{article.Id}", article);
}
}
public interface IArticleRepository : IGenericRepository<Article>
{
}
public abstract class GenericRepository<T> : IGenericRepository<T>
where T : BaseEntity, new()
{
public async Task InsertAsync(T entity)
{
_dbContext.Set<T>().Add(entity);
await _dbContext.SaveChangesAsync();
}
}
--
When i mock the insert , it return 0 as entity.Id , but when i run after hosting the app . The Insert works and return entity.id > 0. What am i doing wrong ? I am pretty new to moq and unit testing
-- Test Method
private Mock<IArticleRepository> _articleRepositoryMock = new Mock<IArticleRepository>();
public ArticlesControllerTests()
{
_articlesController = new ArticlesController(_articleRepositoryMock.Object);
}
[Fact]
public async Task Post_SingleItem()
{
var article = Builder<Article>.CreateNew().Build();
_articleRepositoryMock.As<IGenericRepository<Article>>().Setup(m=>m.InsertAsync(article)).Returns(Task.CompletedTask);
var result = await _articlesController.Post(atricleModel);
//verify after casting the result to desired object and check .Id > 0 fails
}
Apart from .NET 4.5.1 there is a new option on the TransactionScope which enables to use async flow. This allows to write the following client code
using(var txt = new TransactionScope(..., TransactionScopeAsyncFlowOption.Enabled)
{
await sender.SendAsync();
}
So far so good. But when I need to implement a volatile IEnlistmentNotification I'm struggling to do that. Let's imagine the following scenario, assumption: My underlying infrastructure is completely async from bottom to top
public class MessageSender : ISendMessages
{
public async Task SendAsync(TransportMessage message, SendOptions options)
{
await sender.SendAsync(message);
}
}
So what I want to achieve is to introduce a volatile IEnlistmentNotification like this:
internal class SendResourceManager : IEnlistmentNotification
{
private readonly Func<Task> onCommit;
public SendResourceManager(Func<Task> onCommit)
{
this.onCommit = onCommit;
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
preparingEnlistment.Prepared();
}
public void Commit(Enlistment enlistment)
{
await this.onCommit();
enlistment.Done();
}
public void Rollback(Enlistment enlistment)
{
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
enlistment.Done();
}
}
and the new sender
public class MessageSender : ISendMessages
{
public async Task SendAsync(TransportMessage message, SendOptions options)
{
// Dirty: Let's assume Transaction.Current is never null
Transaction.Current.EnlistVolatile(new SendResourceManager(async () => { await sender.SendAsync(message) }));
}
}
Note: Of course this code doesn't compile. It would require me to declare the commit method async void. Which is aweful.
So my question is: How can I write an enlistment which can internally await an asynchronous operation?
As long as EnlistVolatile isn't a heavy CPU bound time consuming operation, you can create a thin Task based wrapper over EnlistVolatile using Task.FromResult:
public static class TranscationExtensions
{
public static Task EnlistVolatileAsync(this Transaction transaction,
IEnlistmentNotification
enlistmentNotification,
EnlistmentOptions enlistmentOptions)
{
return Task.FromResult(transaction.EnlistVolatile
(enlistmentNotification,
enlistmentOptions));
}
}
and then consume it inside your method:
public class MessageSender : ISendMessages
{
public Task SendAsync(TransportMessage message, SendOptions options)
{
return Transaction.Current.EnlistVolatileAsync
(new SendResourceManager(async () =>
{ await sender.SendAsync(message) }));
}
}
which can be awaited higher in your callstack:
MessageSender sender = new MessageSender();
await sender.SendAsync(message, options);