I'm trying to do unit tests on a custom Activity that I have for my MassTransit state machine saga.
It looks something like this:
public class UpdateActivity : Activity<UpdateState>
{
private readonly ConsumeContext _consumeContext;
private readonly ILogger<UpdateActivity> _logger;
public UpdateActivity(
ConsumeContext consumeContext,
ILogger<UpdateActivity> logger
)
{
_consumeContext = consumeContext;
_logger = logger;
}
public void Probe(ProbeContext context) => context.CreateScope(nameof(UpdateActivity));
public void Accept(StateMachineVisitor visitor) => visitor.Visit(this);
public async Task Execute(BehaviorContext<UpdateState> context, Behavior<UpdateState> next)
{
await DoStuffAsync(context.Instance);
await next.Execute(context).ConfigureAwait(false);
}
public async Task Execute<T>(BehaviorContext<UpdateState, T> context, Behavior<UpdateState, T> next)
{
await DoStuffAsync(context.Instance);
await next.Execute(context).ConfigureAwait(false);
}
public Task Faulted<TException>(BehaviorExceptionContext<UpdateState, TException> context, Behavior<UpdateState> next) where TException : Exception
=> next.Faulted(context);
public Task Faulted<T, TException>(BehaviorExceptionContext<UpdateState, T, TException> context, Behavior<UpdateState, T> next) where TException : Exception
=> next.Faulted(context);
}
What I can't figure out is how I can mock/fake expectations for the ConsumeContext when writing unit tests for this class. I've tried to find something using the InMemoryTestHarness but can't find anything suitable.
EDIT:
I might as well throw this one as well in there. How do I mock context or run this in a test harness? So that I can unit test this Activity as well?
public class UpdateActivity : Activity<UpdateState, IDataUpdatedEvent>
{
private readonly ILogger<UpdateActivity> _logger;
public UpdateActivity(
ILogger<UpdateActivity > logger
)
{
_logger = logger;
}
public void Probe(ProbeContext context) => context.CreateScope(nameof(UpdateActivity));
public void Accept(StateMachineVisitor visitor) => visitor.Visit(this);
public async Task Execute(BehaviorContext<UpdateState, IDataUpdatedEvent> context, Behavior<UpdateState, IDataUpdatedEvent> next)
{
MassTransit has test harnesses to allow state machines to be tested, along with activities using Dependency Injection.
The idea of "testing in isolation with mocks" is fairly pointless given the availability of these harnesses.
Related
I have scenario to pass async function as callback to my own resource manager(which implements IEnlistmentNotification interface), and need to invoke asynchronously in prepare method, but it works when invoke as synchronous way, is there any way to make it without wait or asynchronous, the wait producing the AggregatorException rather than my custom exception?
Resource Manager
public class ResourceManager : IEnlistmentNotification
{
private Func<Task>? _doWorkCallback;
public async Task EnlistAsync(Func<Task> doWorkCallback)
{
_doWorkCallback = doWorkCallback;
var transaction = Transaction.Current;
if (transaction != null)
{
await transaction.EnlistVolatileAsync(this, EnlistmentOptions.None).ConfigureAwait(false);
}
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
try
{
_doWorkCallback?.Invoke().Wait();
preparingEnlistment.Prepared();
}
catch
{
preparingEnlistment.ForceRollback();
}
}
public void Commit(Enlistment enlistment)
{
enlistment.Done();
}
public void Rollback(Enlistment enlistment)
{
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
enlistment.Done();
}
}
public static class TranscationExtensions
{
public static Task EnlistVolatileAsync(this Transaction transaction,
IEnlistmentNotification
enlistmentNotification,
EnlistmentOptions enlistmentOptions)
{
return Task.FromResult(transaction.EnlistVolatile
(enlistmentNotification,
enlistmentOptions));
}
}
Usage Code
public class Test
{
private async Task DoWork()
{
Thread.Sleep(1000);// considerer here my custom exception
await Task.CompletedTask;
}
public async Task TestMethod()
{
ResourceManager rm = new ResourceManager();
await rm.EnlistAsync(async () => await DoWork().ConfigureAwait(false)).ConfigureAwait(false);
}
}
Masstransit, but I have such a situation that several message consumers can be launched on one queue, I created a filter that could help me receive the necessary messages for those producers who made the request, but there is a problem that after the message gets into the filter it is marked as read.
Is it possible to make it so that after hitting the filter and its unsuccessful passage, the message remains in the queue.
public class FilterConsumer <TConsumer, TMessage>: IFilter <ConsumerConsumeContext <TConsumer, TMessage>>
where TConsumer: class
where TMessage: class, ICacheKey {
private readonly MemoryCacheHelper _cache;
public FilterConsumer(IMemoryCache cache) {
_cache = new MemoryCacheHelper(cache);
}
public void Probe(ProbeContext context) {
context.CreateFilterScope("filterConsumer");
context.Add("output", "console");
}
public async Task Send(ConsumerConsumeContext <TConsumer, TMessage> context, IPipe <ConsumerConsumeContext < TConsumer, TMessage>> next) {
Console.WriteLine(context.Message.CacheKey());
if (_cache.CheckCache(context.Message.CacheKey()))
await next.Send(context);
else
await context.NotifyConsumed(TimeSpan.Zero, $ "Filtered");
}
}
public class AccountsConsumerDefinition: ConsumerDefinition < AccountsConsumer > {
private readonly IMemoryCache _cache;
public AccountsConsumerDefinition(IMemoryCache cache) {
_cache = cache;
}
protected override void ConfigureConsumer(IReceiveEndpointConfigurator endpointConfigurator, IConsumerConfigurator <AccountsConsumer> consumerConfigurator) {
consumerConfigurator.ConsumerMessage <AccountsBusResponse> (m => m.UseFilter(new FilterConsumer <AccountsConsumer, AccountsBusResponse> (_cache)));
}
}
services.AddMassTransit < TBus > (x => {
if (consumer != null)
x.AddConsumer < TConsumer > (consumerDifinition);
}
I would like to have an equivalent behavior but for Job Consumers :
public class MessageValidatorFilter<T> : IFilter<ConsumeContext<T>>
where T : class
{
private readonly ILogger<MessageValidatorFilter<T>> _logger;
private readonly IValidator<T> _validator;
public MessageValidatorFilter(ILogger<MessageValidatorFilter<T>> logger, IServiceProvider serviceProvider)
{
_logger = logger;
_validator = serviceProvider.GetService<IValidator<T>>();
}
public async Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
{
var validationResult = _validator is not null
? await _validator.ValidateAsync(context.Message, context.CancellationToken)
: new ValidationResult();
if (validationResult.IsValid is false)
{
_logger.LogError("Message validation errors: {Errors}", validationResult.Errors);
await context.Send(
destinationAddress: new($"queue:yourcontext-{KebabCaseEndpointNameFormatter.Instance.SanitizeName(typeof(T).Name)}-validation-errors"),
message: new ValidationResultMessage<T>(context.Message, validationResult));
return;
}
await next.Send(context);
}
public void Probe(ProbeContext context) { }
}
But there is no Middleware for JobConsumers, this documentation (https://masstransit-project.com/advanced/middleware/custom.html) use ConsumeContext which does not work with Job Consumers
You can't, it isn't supported. If you want to validate the message, you'd need to do so in the job consumer, via an injected dependency.
I have added another project to my ABP and I need to access to ApplicationService methods, I've created a module for my new project here is the code
[DependsOn(
typeof(PrestamosApplicationModule),
typeof(PrestamosEntityFrameworkCoreModule))]
public class ReportsApplicationModule : AbpModule
{
public ReportsApplicationModule(PrestamosEntityFrameworkCoreModule abpZeroTemplateEntityFrameworkCoreModule)
{
abpZeroTemplateEntityFrameworkCoreModule.SkipDbContextRegistration = true;
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(ReportsApplicationModule).GetAssembly());
//ServiceCollectionRegistrar.Register(IocManager);
}
public override void PreInitialize()
{
base.PreInitialize();
}
}
The Program.cs class
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureServices((hostContext, services) =>
{
var abpBootstrapper = AbpBootstrapper.Create<ReportsApplicationModule>();
services.AddSingleton(abpBootstrapper);
WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services);
});
}
My problem is when I want to use an AppService
public ReporteClienteController(IClienteAppService clienteAppService)
{
_clienteAppService = clienteAppService;
}
I got the following error
InvalidOperationException: Unable to resolve service for type 'DomiSys.Prestamos.Generales.ClienteNs.IClienteAppService' while attempting to activate 'DomiSys.Prestamos.Reports.Controllers.ReporteClienteController'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
lambda_method(Closure , IServiceProvider , object[] )
Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider+<>c__DisplayClass4_0.b__0(ControllerContext controllerContext)
Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider+<>c__DisplayClass5_0.g__CreateController|0(ControllerContext controllerContext)
Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)
Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
What is wrong?, how can I configure the module correctly to use the depence Injection ?
Can you try replacing PrestamosApplicationModule with PrestamosCoreModule.
And I guess that part of code is not necessary
.ConfigureServices((hostContext, services) =>
{
var abpBootstrapper = AbpBootstrapper.Create<ReportsApplicationModule>();
services.AddSingleton(abpBootstrapper);
WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services);
});
When I want to Insert A New Object into the db bellow Error Occured:
No database provider has been configured for this DbContext
Services:
private IConfiguration config;
public Startup(IConfiguration config) => this.config = config;
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFrameworkSqlServer().AddDbContext<DataContext>(options => options.UseSqlServer(config["ConnectionStrings:MainConnection"]));
services.AddMvc();
}
DataContext:
public class DataContext:DbContext
{
public DataContext() { }
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public DbSet<Request> Request { get; set; }
public DbSet<AdminAccept> AdminAccept { get; set; }
public DbSet<Payment> Payment { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
base.OnConfiguring(builder);
}
}
Insert command :
public async Task <int> SaveToStorageAsync()
{
using (DataContext context=new DataContext())
{
context.Request.Add(this);
return await context.SaveChangesAsync();
}
}
however migrations and database created succefully
I solved it finally.
everything is okay but use of using expression cause an error.(I wonder why)
to solving it first of all I removed a using and declare a DataContext as parameter:
public async Task<int> SaveToStorageAsync(DataContext context)
{
context.Request.Add(this);
return await context.SaveChangesAsync();
}
after it initiate constructor in the main controller :
DataContext context;
public HomeController(DataContext context)
{
this.context = context;
}
and finally call function by sending context as a parameter.
hopped you used in your scenarios and good luck
Since you register the DataContext with the constructor receiving a DbContextOptions<DataContext> option.You also need to pass that when you create a DataContext
var optionsBuilder = new DbContextOptionsBuilder<DataContext >();
optionsBuilder.UseSqlServer("Your connection string");
using (DataContext context = new DataContext (optionsBuilder.Options))
{
context.Request.Add(this);
return await context.SaveChangesAsync();
}
I suggest that you could use dbContext by DI in controller which is a more recommended way in asp.net core:
public class StudentsController : Controller
{
private readonly DataContext _context;
public StudentsController(DataContext context)
{
_context = context;
}
public async Task <int> SaveToStorageAsync()
{
_context.Request.Add(this);
return await context.SaveChangesAsync();
}
}
The two ways are included in below link:
https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext#configuring-dbcontextoptions