I'm trying to write unit tests using MassTransit. When looking online, I found the best way to access the Bus would be creating it using an InMemoryTestHarness. I add my consumers and a PublishObserver to get the resulting behavior.
In the example below, I send a TestRequest message to the bus, then my consumer reads the request and puts a TestResponse message back on the bus. Finally, an observer gets the response.
I don't know if the problem is with some configuration I'm missing, or if there is some task I'm not waiting, but the request message never even arrives at the consumer.
What am I missing?
The test
[TestMethod]
public void RequestResponseBusTest()
{
var harness = new InMemoryTestHarness();
var consumer = new TestConsumer();
harness.OnConfigureInMemoryBus += c =>
{
c.ReceiveEndpoint("testqueue", e =>
e.Consumer(() => consumer));
};
var observer = new TestPublishObserver();
harness.OnConnectObservers += c =>
{
c.ConnectPublishObserver(observer);
};
harness.Start().Wait();
var bus = harness.Bus;
bus.Publish(new TestRequest() { X = 99 }).Wait();
Assert.AreEqual(1, consumer.ConsumedMessages.Count, "consumed");
Assert.AreEqual(1, observer.PublishedRequests.Count, "requests");
Assert.AreEqual(1, observer.PublishedResponses.Count, "responses");
}
And supporting classes
[Serializable]
public class TestRequest
{
public int X { get; set; }
}
[Serializable]
public class TestResponse
{
public int Y { get; set; }
}
public class TestConsumer : IConsumer<TestRequest>
{
public List<TestRequest> ConsumedMessages { get; } = new List<TestRequest>();
public Task Consume(ConsumeContext<TestRequest> context)
{
ConsumedMessages.Add(context.Message);
context.Publish(new TestResponse() { Y = 123 }).Wait();
return Task.CompletedTask;
}
}
private class TestPublishObserver : IPublishObserver
{
public List<TestRequest> PublishedRequests { get; } = new List<TestRequest>();
public List<TestResponse> PublishedResponses { get; } = new List<TestResponse>();
public Task PrePublish<T>(PublishContext<T> context) where T : class
{
return Task.CompletedTask;
}
public Task PostPublish<T>(PublishContext<T> context) where T : class
{
var msg = context.Message;
if (msg is TestRequest)
PublishedRequests.Add((TestRequest)(object)msg);
if (msg is TestResponse)
PublishedResponses.Add((TestResponse)(object)msg);
return Task.CompletedTask;
}
public Task PublishFault<T>(PublishContext<T> context, Exception exception) where T : class
{
return Task.CompletedTask;
}
}
you need to add Thread.Sleep(2000) after bus.Publish(new TestRequest() { X = 99 }).Wait();
bus.Publish does not guarantee message delivery. When you call the Wait() method, you simply wait for it to be sent, not processed
OR!!!
[TestMethod]
public void RequestResponseBusTest()
{
var harness = new InMemoryTestHarness();
var consumer = new TestConsumer();
harness.OnConfigureInMemoryBus += c =>
{
c.ReceiveEndpoint("testqueue", e =>
e.Consumer(() => consumer));
};
var observer = new TestPublishObserver();
harness.OnConnectObservers += c =>
{
c.ConnectPublishObserver(observer);
};
harness.Start().Wait();
var bus = harness.Bus;
bus.Publish(new TestRequest() { X = 99 }).Wait();
//add this line
var receivedMessage = harness.Consumed.Select<TestRequest>().FirstOrDefault();
Assert.AreEqual(1, consumer.ConsumedMessages.Count, "consumed");
Assert.AreEqual(1, observer.PublishedRequests.Count, "requests");
Assert.AreEqual(1, observer.PublishedResponses.Count, "responses");
}
Related
I created a state machine saga that will receive multiple messages and only after a given time period elapses, I want it to continue its work. I figured the only way to do it with mass transit is to go with the scheduling capabilities of the framework.
The saga code (shortened for brevity) is given below:
public class CheckFeedSubmissionStateMachine : MassTransitStateMachine<CheckFeedSubmissionState>
{
public State? WaitingForTimeoutExpiration { get; private set; }
public State? FetchingSubmissionData { get; private set; }
public Event<CheckFeedSubmissionCommand> CheckFeedSubmissionCommandReceived { get; private set; }
public Event<FeedSubmissionListReceivedEvent> FeedSubmissionListReceived { get; private set; }
public Event<FeedSubmissionListErrorReceivedEvent> FeedSubmissionListErrorReceived { get; private set; }
public Event<FeedSubmissionResultReceivedEvent> FeedSubmissionResultReceived { get; private set; }
public Event<FeedSubmissionResultErrorReceivedEvent> FeedSubmissionResultErrorReceived { get; private set; }
public Schedule<CheckFeedSubmissionState, SchedulingCompletionTimeoutExpired> ScheduleCompletionTimeout { get; private set; }
private readonly int _scheduleDelay;
public CheckFeedSubmissionStateMachine(IOptions<SagasOptions> options)
{
_scheduleDelay = int.Parse(options.Value.CheckFeedSubmissionStateMachine["ScheduleDelay"]);
Configure();
BuildProcess();
}
private void Configure()
{
Event(
() => CheckFeedSubmissionCommandReceived,
e => e.CorrelateById(x => x.Message.PartnerGuid));
Schedule(() => ScheduleCompletionTimeout, instance => instance.SchedulingCompletionTimeoutTokenId, s =>
{
s.Delay = TimeSpan.FromSeconds(_scheduleDelay);
s.Received = r => r.CorrelateById(context => context.Message.CorrelationId);
});
InstanceState(state => state.CurrentState);
}
private void BuildProcess()
{
Initially(
When(CheckFeedSubmissionCommandReceived)
.Then(InitializeState)
.Then(StoreSubmissionIds)
.Schedule(ScheduleCompletionTimeout, ScheduleEvent)
.TransitionTo(WaitingForTimeoutExpiration));
During(WaitingForTimeoutExpiration,
When(CheckFeedSubmissionCommandReceived)
.Then(StoreSubmissionIds),
When(ScheduleCompletionTimeout.Received)
.Activity(QueueGetFeedSubmissionListRequest)
.TransitionTo(FetchingSubmissionData));
// the rest ommited for brevity
}
private void InitializeState(BehaviorContext<CheckFeedSubmissionState, CheckFeedSubmissionCommand> ctx) =>
ctx.Instance.PartnerId = ctx.Data.PartnerId;
private void StoreSubmissionIds(BehaviorContext<CheckFeedSubmissionState, CheckFeedSubmissionCommand> ctx)
{
ctx.Instance.SubmissionIdToStatusMap[ctx.Data.FeedSubmissionId] = FeedProcessingStatus.Submitted;
ctx.Instance.SubmissionIdsToCorrelationIdsMap[ctx.Data.FeedSubmissionId] = ctx.Data.CorrelationId;
}
private Task<SchedulingCompletionTimeoutExpired> ScheduleEvent<TEvent>(
ConsumeEventContext<CheckFeedSubmissionState, TEvent> ctx) where TEvent : class =>
ctx.Init<SchedulingCompletionTimeoutExpired>(new { ctx.Instance.CorrelationId });
private EventActivityBinder<CheckFeedSubmissionState, SchedulingCompletionTimeoutExpired> QueueGetFeedSubmissionListRequest(
IStateMachineActivitySelector<CheckFeedSubmissionState, SchedulingCompletionTimeoutExpired> sel) =>
sel.OfType<QueueGetFeedSubmissionListActivity>();
}
The one test that I created for it aims at checking if both published messages have been preserved in the saga, the code below:
[Fact]
public async Task GivenCheckFeedSubmissionCommand_WhenAnotherCheckFeedSubmissionCommandIsReceived_ThenTheSagaStoresBothSubmissionIds()
{
var (harness, sagaHarness) = GetTestComponents();
var partnerGuid = Guid.NewGuid();
await harness.Start();
try
{
await harness.Bus.Publish(GetInitiatingEvent("1", partnerGuid));
await Consumption<CheckFeedSubmissionCommand>(harness, sagaHarness, 1);
await harness.Bus.Publish(GetInitiatingEvent("2", partnerGuid));
await Consumption<CheckFeedSubmissionCommand>(harness, sagaHarness, 2);
var state = sagaHarness.Sagas.Contains(partnerGuid);
state.CurrentState.Should().Be("WaitingForTimeoutExpiration");
state.SubmissionIdsToCorrelationIdsMap.Should().ContainKeys("1", "2");
}
finally
{
await harness.Stop();
}
}
private static (InMemoryTestHarness, IStateMachineSagaTestHarness<CheckFeedSubmissionState, CheckFeedSubmissionStateMachine>) GetTestComponents() =>
TestHarnessFactory.Create<CheckFeedSubmissionState, CheckFeedSubmissionStateMachine>(
sp => sp
.AddSingleton(Options.Create(new SagasOptions
{
CheckFeedSubmissionStateMachine = new Dictionary<string, string>
{
["ScheduleDelay"] = "0"
}
})));
private static CheckFeedSubmissionCommand GetInitiatingEvent(string feedSubmissionId, Guid partnerGuid) =>
new(Guid.NewGuid(), "1", partnerGuid, feedSubmissionId);
private static async Task Consumption<TEvent>(
InMemoryTestHarness harness,
IStateMachineSagaTestHarness<CheckFeedSubmissionState, CheckFeedSubmissionStateMachine> sagaHarness,
int expectedCount)
where TEvent : class
{
if (expectedCount == 1)
{
var harnessConsumed = harness.Consumed.SelectAsync<TEvent>().Any();
var sagaConsumed = sagaHarness.Consumed.SelectAsync<TEvent>().Any();
await Task.WhenAll(harnessConsumed, sagaConsumed);
}
else
{
int harnessConsumedCount;
int sagaConsumedCount;
do
{
var harnessConsumedTask = harness.Consumed.SelectAsync<TEvent>().Count();
var sagaConsumedTask = sagaHarness.Consumed.SelectAsync<TEvent>().Count();
harnessConsumedCount = await harnessConsumedTask;
sagaConsumedCount = await sagaConsumedTask;
await Task.Delay(1000);
} while (harnessConsumedCount < expectedCount && sagaConsumedCount < expectedCount);
}
}
The problem is that when I invoke this line .Schedule(ScheduleCompletionTimeout, ScheduleEvent) in the Initially/When phase, it somehow interferes with state switching and the saga does not switch to the next state - it stays in the Initial state indefinitely. I confirmed it both by inspecting the state variable in the test and by setting a breakpoint in the InitializeState method - it gets hit twice. When I remove that line doing the scheduling, the test passes, though I can't do that, because I need it. Any help?
It's likely you don't have a scheduler configured for the bus with the test harness. If you had logging enabled for the test, you'd see the error in the logs.
The bus configuration for the test harness should let you add the scheduler:
configurator.UseDelayedMessageScheduler();
There is a configuration event on the test harness, OnConfigureInMemoryBus or something like that, which you can use to configure the bus.
I have a method:
public class WorkerClass : IWorkerClass
{
private int myworkId = 0;
public void Workflow(int theWorkid)
{
var aTask = new Task(() =>
{
this.myworkId = theWorkid;
//Some calculations
});
aTask.Start();
}
}
I have a Unit test method :
[DataTestMethod]
[DataRow(23)]
public class workerTester
{
public void WorkflowTest(int theWorkflowMode)
{
// Arrange
var aWorkTester = new Mock<IWorkerClass>();
var aExpectedWorkflowMode = 23;
myWorkerPrivateObj = new PrivateObject(aWorkTester);
// Act
myWorkerPrivateObj .Invoke("Workflow", theWorkflowMode);
// Assert
//Thread.Sleep(1000); If i uncomment it works
var aActualWorkflowModeType = myWorkerPrivateObj.GetField("myworkId");
Assert.AreEqual(aExpectedWorkflowModeType, aActualWorkflowModeType);
}
}
If i uncomment Thread.Sleep(1000), aActualWorkflowModeType comes as 23. But if i comment it aActualWorkflowModeType comes as 0. I am assuming my assert is called before my task is complete.
How can i wait until task is complete in my Unit test?
I figured out a way to do it for unit testing:
var lookupTask = Task<WorkerClass>.Factory.StartNew(() =>
{
return new WorkerClass();
});
lookupTask.Wait();
So my new Unit test method looks like this :
[DataTestMethod]
[DataRow(23)]
public class workerTester
{
public void WorkflowTest(int theWorkflowMode)
{
// Arrange
var aWorkTester = new Mock<IWorkerClass>();
var aExpectedWorkflowMode = 23;
myWorkerPrivateObj = new PrivateObject(aWorkTester);
// Act
myWorkerPrivateObj .Invoke("Workflow", theWorkflowMode);
var lookupTask = Task<WorkerClass>.Factory.StartNew(() =>
{
return new WorkerClass();
});
// Assert
lookupTask.Wait();
var aActualWorkflowModeType = myWorkerPrivateObj.GetField("myworkId");
Assert.AreEqual(aExpectedWorkflowModeType, aActualWorkflowModeType);
}
}
This seems to work
I'm trying to use Rebus in C# as an alternative to Java's org.greenrobot.eventbus.EventBus. Can't figure out how to register an isntance of a handler dynamically.
Goal:
Start the bus
Register handler isntances dynamically at runtime (e.g. add new one when user presses a button)
Receive some messages by those handlers
Stop the bus
Current code:(NUnit) It only prints some diagnostic output from Rebus itself, but not the messages.
using System;
using System.Threading.Tasks;
using Rebus.Config;
using Rebus.Transport.InMem;
using NUnit.Framework;
using Rebus.Activation;
using Rebus.Handlers;
[TestFixture]
public class RebusTests
{
BuiltinHandlerActivator activator = new BuiltinHandlerActivator();
InMemNetwork network = new InMemNetwork(true);
private string inputQueueName = "inputQueue";
[SetUp]
public void Setup() {
Configure.With(activator)
.Transport(t => t.UseInMemoryTransport(network, inputQueueName))
.Start();
}
[TearDown]
public void Cleanup() {
activator.Dispose();
}
[Test]
public void TestHandlingStrings() {
var h1 = new StringHandler("handler 1");
var h2 = new StringHandler("handler 2");
int workersCount = activator.Bus.Advanced.Workers.Count;
activator.Bus.Advanced.Workers.SetNumberOfWorkers(0);
activator.Register(() => h1);
activator.Register(() => h2);
activator.Bus.Advanced.Workers.SetNumberOfWorkers(workersCount);
activator.Bus.Advanced.SyncBus.SendLocal("Good day, sir.");
}
}
public class StringHandler : IHandleMessages<string> {
public readonly string handlerName;
public StringHandler(string handlerName) {
this.handlerName = handlerName;
}
public Task Handle(string message) {
Console.WriteLine($"1) Handler [{handlerName}] got: {message}");
return Task.Run(() => { Console.WriteLine($"2) Handler [{handlerName}] got: {message}"); });
}
}
The code
[Test]
public void TestHandlingStrings() {
var h1 = new StringHandler("handler 1");
var h2 = new StringHandler("handler 2");
int workersCount = activator.Bus.Advanced.Workers.Count;
activator.Bus.Advanced.Workers.SetNumberOfWorkers(0);
activator.Register(() => h1);
activator.Register(() => h2);
activator.Bus.Advanced.Workers.SetNumberOfWorkers(workersCount);
activator.Bus.Advanced.SyncBus.SendLocal("Good day, sir.");
}
will exit almost immediately, so the bus is likely to never get to receive anything.
If you insert a little Thread.Sleep(TimeSpan.FromSeconds(2)); at the end of the test, I bet your message will be received:
[Test]
public void TestHandlingStrings() {
var h1 = new StringHandler("handler 1");
var h2 = new StringHandler("handler 2");
int workersCount = activator.Bus.Advanced.Workers.Count;
activator.Bus.Advanced.Workers.SetNumberOfWorkers(0);
activator.Register(() => h1);
activator.Register(() => h2);
activator.Bus.Advanced.Workers.SetNumberOfWorkers(workersCount);
activator.Bus.Advanced.SyncBus.SendLocal("Good day, sir.");
Thread.Sleep(TimeSpan.FromSeconds(2));
}
I am trying to implement a simple example/demo for a state machine using Automatonymous with RabbitMQ. Unfortunately I could not find one to rebuild / learn from (I found the ShoppingWeb, but in my eyes it's anything but simple). Also in my opinion the documentation is lacking information.
This is the state machine example I thought of (sorry, it's pretty ugly):
Please note that this example is completely made up and it's not important if it makes sense or not. This project's purpose is to get "warm" with Automatonymous.
What I want to do / to have is:
Four applications running:
The state machine itself
The "requester" sending requests to be interpreted
The "validator" or "parser" checking if the provided request is valid
The "interpreter" interpreting the given request
An example of this could be:
Requester sends "x=5"
Validator checks if a "=" is contained
Intepreter says "5"
My implementation of the state machine looks like this:
public class InterpreterStateMachine : MassTransitStateMachine<InterpreterInstance>
{
public InterpreterStateMachine()
{
InstanceState(x => x.CurrentState);
Event(() => Requesting, x => x.CorrelateBy(request => request.Request.RequestString, context => context.Message.Request.RequestString)
.SelectId(context => Guid.NewGuid()));
Event(() => Validating, x => x.CorrelateBy(request => request.Request.RequestString, context => context.Message.Request.RequestString));
Event(() => Interpreting, x => x.CorrelateBy(request => request.Request.RequestString, context => context.Message.Request.RequestString));
Initially(
When(Requesting)
.Then(context =>
{
context.Instance.Request = new Request(context.Data.Request.RequestString);
})
.ThenAsync(context => Console.Out.WriteLineAsync($"Request received: {context.Data.Request.RequestString}"))
.Publish(context => new ValidationNeededEvent(context.Instance))
.TransitionTo(Requested)
);
During(Requested,
When(Validating)
.Then(context =>
{
context.Instance.Request.IsValid = context.Data.Request.IsValid;
if (!context.Data.Request.IsValid)
{
this.TransitionToState(context.Instance, Error);
}
else
{
this.TransitionToState(context.Instance, RequestValid);
}
})
.ThenAsync(context => Console.Out.WriteLineAsync($"Request '{context.Data.Request.RequestString}' validated with {context.Instance.Request.IsValid}"))
.Publish(context => new InterpretationNeededEvent(context.Instance))
,
Ignore(Requesting),
Ignore(Interpreting)
);
During(RequestValid,
When(Interpreting)
.Then((context) =>
{
//do something
})
.ThenAsync(context => Console.Out.WriteLineAsync($"Request '{context.Data.Request.RequestString}' interpreted with {context.Data.Answer}"))
.Publish(context => new AnswerReadyEvent(context.Instance))
.TransitionTo(AnswerReady)
.Finalize(),
Ignore(Requesting),
Ignore(Validating)
);
SetCompletedWhenFinalized();
}
public State Requested { get; private set; }
public State RequestValid { get; private set; }
public State AnswerReady { get; private set; }
public State Error { get; private set; }
//Someone is sending a request to interprete
public Event<IRequesting> Requesting { get; private set; }
//Request is validated
public Event<IValidating> Validating { get; private set; }
//Request is interpreted
public Event<IInterpreting> Interpreting { get; private set; }
class ValidationNeededEvent : IValidationNeeded
{
readonly InterpreterInstance _instance;
public ValidationNeededEvent(InterpreterInstance instance)
{
_instance = instance;
}
public Guid RequestId => _instance.CorrelationId;
public Request Request => _instance.Request;
}
class InterpretationNeededEvent : IInterpretationNeeded
{
readonly InterpreterInstance _instance;
public InterpretationNeededEvent(InterpreterInstance instance)
{
_instance = instance;
}
public Guid RequestId => _instance.CorrelationId;
}
class AnswerReadyEvent : IAnswerReady
{
readonly InterpreterInstance _instance;
public AnswerReadyEvent(InterpreterInstance instance)
{
_instance = instance;
}
public Guid RequestId => _instance.CorrelationId;
}
}
Then I have services like this:
public class RequestService : ServiceControl
{
readonly IScheduler scheduler;
IBusControl busControl;
BusHandle busHandle;
InterpreterStateMachine machine;
InMemorySagaRepository<InterpreterInstance> repository;
public RequestService()
{
scheduler = CreateScheduler();
}
public bool Start(HostControl hostControl)
{
Console.WriteLine("Creating bus...");
machine = new InterpreterStateMachine();
repository = new InMemorySagaRepository<InterpreterInstance>();
busControl = Bus.Factory.CreateUsingRabbitMq(x =>
{
IRabbitMqHost host = x.Host(new Uri(/*rabbitMQ server*/), h =>
{
/*credentials*/
});
x.UseInMemoryScheduler();
x.ReceiveEndpoint(host, "interpreting_answer", e =>
{
e.PrefetchCount = 5; //?
e.StateMachineSaga(machine, repository);
});
x.ReceiveEndpoint(host, "2", e =>
{
e.PrefetchCount = 1;
x.UseMessageScheduler(e.InputAddress);
//Scheduling !?
e.Consumer(() => new ScheduleMessageConsumer(scheduler));
e.Consumer(() => new CancelScheduledMessageConsumer(scheduler));
});
});
Console.WriteLine("Starting bus...");
try
{
busHandle = MassTransit.Util.TaskUtil.Await<BusHandle>(() => busControl.StartAsync());
scheduler.JobFactory = new MassTransitJobFactory(busControl);
scheduler.Start();
}
catch (Exception)
{
scheduler.Shutdown();
throw;
}
return true;
}
public bool Stop(HostControl hostControl)
{
Console.WriteLine("Stopping bus...");
scheduler.Standby();
if (busHandle != null) busHandle.Stop();
scheduler.Shutdown();
return true;
}
static IScheduler CreateScheduler()
{
ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
IScheduler scheduler = MassTransit.Util.TaskUtil.Await<IScheduler>(() => schedulerFactory.GetScheduler()); ;
return scheduler;
}
}
My questions are:
How do I send the "intial" request, so that the state machine will transition to my initial state
How do I "react" within the consumers to check the data that were sent and then send new data like in 1?
Okay I figured it out. I probably had problems because I'm not only new to Masstransit/Automatonymous and RabbitMQ, but also don't have much experience with C# yet.
So if anyone ever will have the same problem, here is what you need:
Given the above example there are three different types plus some small interfaces needed:
A sender (in this case the "requester") including a specific consumer
A service that consumes specific message types (the "validator" and "interpreter")
A service that holds the state machine without a specific consumer
Some "contracts", which are interfaces defining the type of message that's sent/consumed
1) This is the sender:
using InterpreterStateMachine.Contracts;
using MassTransit;
using System;
using System.Threading.Tasks;
namespace InterpreterStateMachine.Requester
{
class Program
{
private static IBusControl _busControl;
static void Main(string[] args)
{
var busControl = ConfigureBus();
busControl.Start();
Console.WriteLine("Enter request or quit to exit: ");
while (true)
{
Console.Write("> ");
String value = Console.ReadLine();
if ("quit".Equals(value,StringComparison.OrdinalIgnoreCase))
break;
if (value != null)
{
String[] values = value.Split(';');
foreach (String v in values)
{
busControl.Publish<IRequesting>(new
{
Request = new Request(v),
TimeStamp = DateTime.UtcNow
});
}
}
}
busControl.Stop();
}
static IBusControl ConfigureBus()
{
if (null == _busControl)
{
_busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri(/*rabbitMQ server*/), h =>
{
/*credentials*/
});
cfg.ReceiveEndpoint(host, "answer_ready", e =>
{
e.Durable = true;
//here the consumer is registered
e.Consumer<AnswerConsumer>();
});
});
_busControl.Start();
}
return _busControl;
}
//here comes the actual logic of the consumer, which consumes a "contract"
class AnswerConsumer : IConsumer<IAnswerReady>
{
public async Task Consume(ConsumeContext<IAnswerReady> context)
{
await Console.Out.WriteLineAsync($"\nReceived Answer for \"{context.Message.Request.RequestString}\": {context.Message.Answer}.");
await Console.Out.WriteAsync(">");
}
}
}
}
2) This is the service (here it is the validation sercive)
using InterpreterStateMachine.Contracts;
using MassTransit;
using MassTransit.QuartzIntegration;
using MassTransit.RabbitMqTransport;
using Quartz;
using Quartz.Impl;
using System;
using System.Threading.Tasks;
using Topshelf;
namespace InterpreterStateMachine.Validator
{
public class ValidationService : ServiceControl
{
readonly IScheduler _scheduler;
static IBusControl _busControl;
BusHandle _busHandle;
public static IBus Bus => _busControl;
public ValidationService()
{
_scheduler = CreateScheduler();
}
public bool Start(HostControl hostControl)
{
Console.WriteLine("Creating bus...");
_busControl = MassTransit.Bus.Factory.CreateUsingRabbitMq(x =>
{
IRabbitMqHost host = x.Host(new Uri(/*rabbitMQ server*/), h =>
{
/*credentials*/
});
x.UseInMemoryScheduler();
x.UseMessageScheduler(new Uri(RabbitMqServerAddress));
x.ReceiveEndpoint(host, "validation_needed", e =>
{
e.PrefetchCount = 1;
e.Durable = true;
//again this is how the consumer is registered
e.Consumer<RequestConsumer>();
});
});
Console.WriteLine("Starting bus...");
try
{
_busHandle = MassTransit.Util.TaskUtil.Await<BusHandle>(() => _busControl.StartAsync());
_scheduler.JobFactory = new MassTransitJobFactory(_busControl);
_scheduler.Start();
}
catch (Exception)
{
_scheduler.Shutdown();
throw;
}
return true;
}
public bool Stop(HostControl hostControl)
{
Console.WriteLine("Stopping bus...");
_scheduler.Standby();
_busHandle?.Stop();
_scheduler.Shutdown();
return true;
}
static IScheduler CreateScheduler()
{
ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
IScheduler scheduler = MassTransit.Util.TaskUtil.Await<IScheduler>(() => schedulerFactory.GetScheduler());
return scheduler;
}
}
//again here comes the actual consumer logic, look how the message is re-published after it was checked
class RequestConsumer : IConsumer<IValidationNeeded>
{
public async Task Consume(ConsumeContext<IValidationNeeded> context)
{
await Console.Out.WriteLineAsync($"(c) Received {context.Message.Request.RequestString} for validation (Id: {context.Message.RequestId}).");
context.Message.Request.IsValid = context.Message.Request.RequestString.Contains("=");
//send the new message on the "old" context
await context.Publish<IValidating>(new
{
Request = context.Message.Request,
IsValid = context.Message.Request.IsValid,
TimeStamp = DateTime.UtcNow,
RequestId = context.Message.RequestId
});
}
}
}
The validator consumes the contract "IValidationNeeded" and then publishes the contract "IValidating", which then will be consumed by the state machine itself (the "Validating" event).
3) The difference between a consumer service and the sate machine service lies withing the "ReceiveEndpoint". Here is no consumer registered, but the state machine is set:
...
InterpreterStateMachine _machine = new InterpreterStateMachine();
InMemorySagaRepository<InterpreterInstance> _repository = new InMemorySagaRepository<InterpreterInstance>();
...
x.ReceiveEndpoint(host, "state_machine", e =>
{
e.PrefetchCount = 1;
//here the state machine is set
e.StateMachineSaga(_machine, _repository);
e.Durable = false;
});
4) Last but not least, the contracts are pretty small and look like this:
using System;
namespace InterpreterStateMachine.Contracts
{
public interface IValidationNeeded
{
Guid RequestId { get; }
Request Request { get; }
}
}
So overall it's pretty straightforward, I just had to use my brain :D
I hope this will help someone.
enter code hereI have following method where i need to write unit test case to test this method.
public void ProcessDormantApplications()
{
List<long> dormantApplicationIDs = new List<long>();
dormantApplicationIDs = service.GetDormantApplications();
if (dormantApplicationIDs.Count > 0)
{
foreach (long dormantID in dormantApplicationIDs)
{
string msg = service.UpdateDormantApplications(dormantID);
}
}
}
}
and this is the TEST method i wrote.
[TestClass]
public class DormantApplicationsTest
{
ILogger logger;
IService Service;
[TestInitialize]
public void SetUp()
{
logger = MockRepository.GenerateStub<ILogger>();
Service = MockRepository.GenerateStub<IService>();
}
[TestMethod]
public void TESTProcessDormantApplications()
{
////arrange
////act
var target = new BLogic(service, logger);
target.ProcessDormantApplications();
////assert
// service.AssertWasCalled(x => x.
}
}
The actual method calls another service layer which inturn invokes web service to get data. In this scenario I am not sure what to ASSERT in this situation.
[TestMethod]
public void CheckProcessDormantApplications_InBetweenApplicationFailedToUpdate()
{
////arrange
var applicationIds = new List<long>()
{
1,2,3
};
UpdateResponse.isSuccess = true;
UpdateResponse.errorMessage = string.Empty;
Service.Stub(x => x.GetDormantApplications()).Return(applicationIds);
for(int i=0; i <= applicationIds.Count-1; i++)
{
if (i == 1) //set this application id response to FALSE so it should continnue with next record as well
{
UpdateResponse.isSuccess = false;
UpdateResponse.errorMessage = "making it fail for test";
}
Service.Stub(x =>x.UpdateDormantApplications(applicationIds[i])).Return(UpdateResponse);
}
////act
var target = new BLogic(Service, logger);
target.ProcessDormantApplications();
////assert
foreach (long id in applicationIds)
{
Service.AssertWasCalled(x => x.UpdateDormantApplications(id));
}
}
}
Based on the code you provide you have to set a behavior on GetDormantApplications which return some ids, then verify that UpdateDormantApplications was called for each id:
[TestMethod]
public void Check_ProcessDormantApplications()
{
////arrange
var applicationId = new List<long>()
{
//put here some ids
};
DormantServiceAdapter.Stub(x => x.GetDormantApplications()).Return(applicationId);
var target = new DormantApplicationsBusinessLogic(DormantServiceAdapter, logger);
////act
target.ProcessDormantApplications();
////assert
foreach (var id in applicationId)
{
DormantServiceAdapter.AssertWasCalled(x => x.UpdateDormantApplications(id));
}
}