How to successfully drive a MassTransitStateMachine via the InMemoryTestHarness? - c#

Following up to: How to write MassTransitStateMachine unit tests?
Here's a simple test class (using MS Test) for a simple state machine called ProcedureStateMachine (note: this is not a real production state machine for us... just an experiment I'd used to play around with MassTransitStateMachine a while back.. it seemed a handy self-contained place to experiment with getting unit testing going too):
[TestClass]
public class ProcedureStateMachineTests
{
private ProcedureStateMachine _machine;
private InMemoryTestHarness _harness;
private StateMachineSagaTestHarness<ProcedureContext, ProcedureStateMachine> _saga;
[TestInitialize]
public void SetUp()
{
_machine = new ProcedureStateMachine();
_harness = new InMemoryTestHarness();
_saga = _harness.StateMachineSaga<ProcedureContext, ProcedureStateMachine>(_machine);
_harness.Start().Wait();
}
[TestCleanup]
public void TearDown()
{
_harness.Stop().Wait();
}
[TestMethod]
public async Task That_Can_Start()
{
// Arrange
// Act
await _harness.InputQueueSendEndpoint.Send(new BeginProcessing
{
ProcedureId = Guid.NewGuid(),
Steps = new List<string> {"A", "B", "C" }
});
// Assert
var sagaContext = _saga.Created.First();
sagaContext.Saga.RemainingSteps.ShouldHaveCountOf(2);
}
}
And here's the state machine class itself:
public class ProcedureStateMachine : MassTransitStateMachine<ProcedureContext>
{
public State Processing { get; private set; }
public State Cancelling { get; private set; }
public State CompleteOk { get; private set; }
public State CompleteError { get; private set; }
public State CompleteCancelled { get; private set; }
public Event<BeginProcessing> Begin { get; private set; }
public Event<StepCompleted> StepDone { get; private set; }
public Event<CancelProcessing> Cancel { get; private set; }
public Event<FinalizeProcessing> Finalize { get; private set; }
public ProcedureStateMachine()
{
InstanceState(x => x.CurrentState);
Event(() => Begin);
Event(() => StepDone);
Event(() => Cancel);
Event(() => Finalize);
BeforeEnterAny(binder => binder
.ThenAsync(context => Console.Out.WriteLineAsync(
$"ENTERING STATE [{context.Data.Name}]")));
Initially(
When(Begin)
.Then(context =>
{
context.Instance.RemainingSteps = new Queue<string>(context.Data.Steps);
})
.ThenAsync(context => Console.Out.WriteLineAsync(
$"EVENT [{nameof(Begin)}]: Procedure [{context.Data.ProcedureId}] Steps [{string.Join(",", context.Data.Steps)}]"))
.Publish(context => new ExecuteStep
{
ProcedureId = context.Instance.CorrelationId,
StepId = context.Instance.RemainingSteps.Dequeue()
})
.Publish(context => new SomeFunMessage
{
CorrelationId = context.Data.CorrelationId,
TheMessage = $"Procedure [{context.Data.CorrelationId} has begun..."
})
.TransitionTo(Processing)
);
During(Processing,
When(StepDone)
.Then(context =>
{
if (null == context.Instance.AccumulatedResults)
{
context.Instance.AccumulatedResults = new List<StepResult>();
}
context.Instance.AccumulatedResults.Add(
new StepResult
{
CorrelationId = context.Instance.CorrelationId,
StepId = context.Data.StepId,
WhatHappened = context.Data.WhatHappened
});
})
.ThenAsync(context => Console.Out.WriteLineAsync(
$"EVENT [{nameof(StepDone)}]: Procedure [{context.Data.ProcedureId}] Step [{context.Data.StepId}] Result [{context.Data.WhatHappened}] RemainingSteps [{string.Join(",", context.Instance.RemainingSteps)}]"))
.If(context => !context.Instance.RemainingSteps.Any(),
binder => binder.TransitionTo(CompleteOk))
.If(context => context.Instance.RemainingSteps.Any(),
binder => binder.Publish(context => new ExecuteStep
{
ProcedureId = context.Instance.CorrelationId,
StepId = context.Instance.RemainingSteps.Dequeue()
})),
When(Cancel)
.Then(context =>
{
context.Instance.RemainingSteps.Clear();
})
.ThenAsync(context => Console.Out.WriteLineAsync(
$"EVENT [{nameof(Cancel)}]: Procedure [{context.Data.ProcedureId}] will be cancelled with following steps remaining [{string.Join(",", context.Instance.RemainingSteps)}]"))
.TransitionTo(Cancelling)
);
During(Cancelling,
When(StepDone)
.Then(context =>
{
context.Instance.SomeStringValue = "Booo... we cancelled...";
})
.ThenAsync(context => Console.Out.WriteLineAsync(
$"EVENT [{nameof(StepDone)}]: Procedure [{context.Data.ProcedureId}] Step [{context.Data.StepId}] completed while cancelling."))
.TransitionTo(CompleteCancelled));
During(CompleteOk, When(Finalize).Finalize());
During(CompleteCancelled, When(Finalize).Finalize());
During(CompleteError, When(Finalize).Finalize());
// The "SetCompleted*" thing is what triggers purging of the state context info from the store (eg. Redis)... without that, the
// old completed state keys will gradually accumulate and dominate the Redis store.
SetCompletedWhenFinalized();
}
}
When debug this test, the _harness has the BeginProcessing message in its Sent collection, but there's nothing in the _saga.Created collection. It seems like I'm missing some plumbing to cause the harness to actually drive the state machine when the messages are sent?
====
Removing the .Wait() calls from SetUp() and TearDown() and updating the test to the following does NOT change the behavior:
[TestMethod]
public async Task That_Can_Start()
{
try
{
await _harness.Start();
// Arrange
// Act
await _harness.InputQueueSendEndpoint.Send(new BeginProcessing
{
ProcedureId = Guid.NewGuid(),
Steps = new List<string> {"A", "B", "C"}
});
// Assert
var sagaContext = _saga.Created.First();
sagaContext.Saga.RemainingSteps.ShouldHaveCountOf(3);
}
finally
{
await _harness.Stop();
}
}

It turns out that the test code as shown above was suffering from a race condition between the _harness.InputQueueSendEndpoint.Send operation and some asynchronous (beyond what await on the Send waits for) behavior in the StateMachineSagaTestHarness. As a result, the "Assert" phase of the test code was executing before the saga had been created and allowed to handle the sent message.
Digging into the SagaTestHarness code a bit, I found a few helper methods that I was able to use to wait until certain conditions on the saga are met. The methods are:
/// <summary>
/// Waits until a saga exists with the specified correlationId
/// </summary>
/// <param name="sagaId"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public async Task<Guid?> Exists(Guid sagaId, TimeSpan? timeout = null)
/// <summary>
/// Waits until at least one saga exists matching the specified filter
/// </summary>
/// <param name="filter"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public async Task<IList<Guid>> Match(Expression<Func<TSaga, bool>> filter, TimeSpan? timeout = null)
/// <summary>
/// Waits until the saga matching the specified correlationId does NOT exist
/// </summary>
/// <param name="sagaId"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public async Task<Guid?> NotExists(Guid sagaId, TimeSpan? timeout = null)
So I settled on using things like await _saga.Match(s => null != s.RemainingSteps); and such to effectively duplicate my later asserts and wait either until the timeout (default is 30 seconds) or the later-asserted condition has become true (and therefore safe to Assert against).. whichever comes first.
This will get me unstuck for now until I can think of a better way to know when the harness is "caught up" and ready to be interrogated.

Related

Update React front-end from .Net back-end

I am recently approaching React and I was wondering how can I send from my ASP.NET back-end some updates to all the front-ends, but first let me explain myself.
I am sending some inputs from one front-end to the back-end. Just for reference here's how I do it:
Front-end
async SendInput(data) {
await fetch('Status/ChangeState', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
Back-end
[ApiController]
[Route("[controller]")]
public class StatusController : Controller
{
[HttpPost("ChangeState")]
public void ChangeState([FromBody] short i)
{
//Do stuff here
}
}
Now what I need is keep all the connected users variables updated when this "state" is changed by one of them.
My first thought was to send repeatedly a POST or GET request to the back-end with setInterval(), but even if there are not so many users I still would prefer a method like Long polling or Server Sent Events.
What would a good recommended approach be? If possible include an example.
The simplest possible state manager in my opinion for long polling can be created with a TaskCompletionSource. The controller waits for a state change with await and sends the result as ActionResult. The client opens the connection to this controller immediately after receiving the data.
However, I would only use this implementation for very small projects and is intended more as an illustrative example. For larger projects SignalR can be used.
/// <summary>
/// Class for your state properties.
/// </summary>
public class State
{
public string MyStateField1 { get; set; } = string.Empty;
public string MyStateField2 { get; set; } = string.Empty;
}
/// <summary>
/// StateManager
/// Register with
/// services.AddSingleton<StateManager<State>>();
/// in ConfigureServices().
/// </summary>
/// <typeparam name="T">Type of the managed state class.</typeparam>
public class StateManager<T> where T : new()
{
private readonly T _state;
private TaskCompletionSource<T> _taskCompletition = new TaskCompletionSource<T>();
private readonly object _taskCompletitionLock = new object();
public StateManager()
{
_state = new T();
}
public void ChangeState(Action<T> updateFunction)
{
lock (_taskCompletitionLock)
{
updateFunction(_state);
_taskCompletition.SetResult(_state);
_taskCompletition = new TaskCompletionSource<T>();
}
}
public Task<T> GetChangedState() => _taskCompletition.Task;
}
Register in Startup.ConfigureServices:
services.AddSingleton<StateManager<State>>();
Usage (in your controller):
public async Task<IActionResult> StateMonitor()
{
var newState = await _stateManager.GetChangedState();
return Ok(newState);
}
public async Task<IActionResult> SetNewState()
{
_stateManager.ChangeState(state => state.MyStateField1 = "My changed State");
return NoContentResult();
}

How to write xUnit Test for a method which calls another method in its body?

This is the class contains EnqueueJobAsync method which I want to write test for it :
public class ConsumerBaseForTesting
{
protected IJobStore JobStore { get; private set; }
public ConsumerBaseForTesting(IJobStore jobStore)
{
JobStore = jobStore;
}
public async Task<IJob> EnqueueJobAsync(IJob job)
=> await JobStore.CreateAsync(job);
}
This is my test which Fails and its actual return is always NULL !
public class ConsumerBaseTest
{
private readonly Mock<IJobStore> _moqIJobStore;
private readonly ConsumerBaseForTesting _consumerBase;
public ConsumerBaseTest()
{
_moqIJobStore = new Mock<IJobStore>();
_consumerBase = new ConsumerBaseForTesting(_moqIJobStore.Object);
}
[Theory]
[ClassData(typeof(JobClassForTesting))]
public async Task EnqueueJobAsyncTest(IJob job)
{
var jobResult = await _consumerBase.EnqueueJobAsync(job);
Assert.Equal(job, jobResult);
}
}
The mock needs to be setup to do two things in order to replicate the expected behavior.
It needs to return the passed job in a completed task.
//...
public ConsumerBaseTest() {
_moqIJobStore = new Mock<IJobStore>();
_consumerBase = new ConsumerBaseForTesting(_moqIJobStore.Object);
//setup the mock to capture and return the job when CreateAsync(IJob job) is invoked
_moqIJobStore
.Setup(_ => _.CreateAsync(It.IsAny<IJob>()))
.Returns((IJob x) => Task.FromResult(x)); }
//...
.Returns((IJob x) => Task.FromResult(x)) captures the argument and returns completed Task<IJob>

Cannot setup a Moq callback for MediatR

I'm writing a test that verifies if my controller calls my query with expected query arguments. This is my query class:
public class GetProducts : IRequest<IEnumerable<Product>>
{
public int CategoryId { get; set; }
}
This implements IRequest<T> MediatR interface.
This is the test case that does not work:
[Theory]
[InlineData(1)]
public async Task GetProductsAsync_GivenValidRequestParameters_ReturnsListGetProductsResponseAsync(int categoryId)
{
var expectedQuery = new GetProducts
{
CategoryId = categoryId
};
_mediatorMock
.Setup(s => s.Send(It.IsAny<GetProducts>(), It.IsAny<CancellationToken>()))
.Callback<GetProducts, CancellationToken>((query, ct) => query.Should().BeEquivalentTo(expectedQuery))
.ReturnsAsync(Enumerable.Empty<Product>());
var response = await _productsController.GetProductsAsync(categoryId);
response.Result.Should().BeOfType<OkObjectResult>();
_mediatorMock.Verify(s => s.Send(It.IsAny<GetProducts>(), It.IsAny<CancellationToken>()), Times.Once);
}
This is the controller I'm testing:
[ApiController]
[Route("categories/{categoryId:int}/products")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[TrackUsage]
public class ProductsController : ControllerBase
{
private readonly IMediator _mediator;
public ProductsController(IMediator mediator)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
}
[HttpGet]
public async Task<ActionResult<IEnumerable<GetProductsResponse>>> GetProductsAsync([FromRoute]int categoryId)
{
var query = new GetProducts
{
CategoryId = categoryId
};
var products = await _mediator.Send(query);
return Ok(products.ToResponse());
}
}
It complains because it cannot find an callback with <GetProducts, CancellationToken> as parameters even though it seems right.
I know I could use It.Is<...>(callback => true) to check each and every property, but there could be queries with multiple properties and I'd prefer to test that using FluentAssertion.
Generic Send definition
public interface IMediator
{
/// <summary>
/// Asynchronously send a request to a single handler
/// </summary>
/// <typeparam name="TResponse">Response type</typeparam>
/// <param name="request">Request object</param>
/// <param name="cancellationToken">Optional cancellation token</param>
/// <returns>A task that represents the send operation. The task result contains the handler response</returns>
Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);
//...
Source
The Callback needs to match the provided definition
//...
_mediatorMock
.Setup(s => s.Send<IEnumerable<Product>>(It.IsAny<IRequest<IEnumerable<Product>>>(), It.IsAny<CancellationToken>()))
.Callback<IRequest<IEnumerable<Product>>, CancellationToken>((query, ct) =>
((GetProducts)query).Should().BeEquivalentTo(expectedQuery)
)
.ReturnsAsync(Enumerable.Empty<Product>());
//...

WebAPI + OWIN + SignalR + Autofac

I have been struggling on this issue for weeks now.
I have an app where i have configured owin backend with web api and autofac DI with background handfire jobs. I have alsmost looked at every question on Stackoveflow regarding this but nothing seems to work.
My app regarding OWIN/Hangfire/WebAPI all seems to work okay. Until it comes to SignalR push messages.
If i call any notification hub endpoint from js client push messages go okay and i can receive push messages on any other connected client. But when i wan to send message from my api controller or hangfire job it never reaches to any client.
Startup.cs
public void Configuration(IAppBuilder app)
{
//var signalRHelper = new SignalRHelper(GlobalHost.ConnectionManager.GetHubContext<NotificationHub>());
var constants = new Constants();
constants.Set(ConstantTypes.AllyHrNoReplyEmailAddress, Util.Constants.AllyHrNoReplyEmailAddress);
constants.Set(ConstantTypes.SendGridKey, Util.Constants.SendGridKey);
constants.Set(ConstantTypes.EncryptionKey, Util.Constants.EncryptionKey);
constants.Set(ConstantTypes.ApiUrl, Util.Constants.ApiUrl);
constants.Set(ConstantTypes.RootFolder, Util.Constants.RootFolder);
constants.Set(ConstantTypes.FrontEndUrl, Util.Constants.FrontEndUrl);
GlobalConfiguration.Configuration
.UseSqlServerStorage("AllyHrDb");
var config = System.Web.Http.GlobalConfiguration.Configuration;
var builder = new ContainerBuilder();
var jobBuilder = new ContainerBuilder();
var signalRBuilder = new ContainerBuilder();
var hubConfig = new HubConfiguration();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired();
builder.Register(x => constants);
builder.RegisterModule(new ServiceModule());
jobBuilder.Register(x => constants);
jobBuilder.RegisterModule(new HangfireServiceModule());
signalRBuilder.RegisterModule(new SignalRServiceModule());
signalRBuilder.Register(x => constants);
signalRBuilder.RegisterType<AutofacDependencyResolver>().As<IDependencyResolver>().SingleInstance();
signalRBuilder.RegisterType<ConnectionManager>().As<IConnectionManager>().ExternallyOwned().SingleInstance();
signalRBuilder.RegisterType<NotificationHub>().ExternallyOwned().SingleInstance();
signalRBuilder.RegisterType<SignalRHelper>().PropertiesAutowired().ExternallyOwned().SingleInstance();
signalRBuilder.Register(context => context.Resolve<IDependencyResolver>().Resolve<IConnectionManager>().GetHubContext<NotificationHub, INotificationHub>()).ExternallyOwned().SingleInstance();
var hubContainer = signalRBuilder.Build();
builder.RegisterInstance(hubContainer.Resolve<IConnectionManager>());
builder.RegisterInstance(hubContainer.Resolve<IHubContext<INotificationHub>>());
builder.RegisterInstance(hubContainer.Resolve<NotificationHub>());
builder.RegisterInstance(hubContainer.Resolve<SignalRHelper>());
jobBuilder.RegisterInstance(hubContainer.Resolve<IHubContext<INotificationHub>>());
jobBuilder.RegisterInstance(hubContainer.Resolve<NotificationHub>());
jobBuilder.RegisterInstance(hubContainer.Resolve<SignalRHelper>());
var container = builder.Build();
var jobContainer = jobBuilder.Build();
var idProvider = new SignalRCustomUserIdProvider();
hubConfig.Resolver = new AutofacDependencyResolver(hubContainer);
hubConfig.Resolver.Register(typeof(IUserIdProvider), () => idProvider);
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new QueryStringOAuthBearerProvider()
});
map.RunSignalR(hubConfig);
});
GlobalConfiguration.Configuration.UseAutofacActivator(jobContainer);
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
app.UseHangfireServer();
app.UseHangfireDashboard();
ConfigureAuth(app);
app.UseWebApi(config);
}
I had to use different container because i have db set to InstancePerRequest scope.
All my services are being resolved in notification hub class, no problems there. The only issues is when i try and send message from hangfire service or even from api controller using hub context it never reaches to any client.
NotificationHub.cs
public interface INotificationHub
{
/// <summary>
///
/// </summary>
void pushNotification(string message);
/// <summary>
///
/// </summary>
/// <param name="model"></param>
void getNotification(object model);
void getMessage(object model);
}
/// <summary>
/// Notification Hub
/// </summary>
[HubName("NotificationHub")]
[Authorize]
public class NotificationHub : Hub<INotificationHub>
{
/// <summary>
///
/// </summary>
public static IHubContext<INotificationHub> GlobalContext { get; private set; }
private readonly IChatMessagingService _chatMessagingService;
private readonly IUserService _userService;
private Guid LoggedInUserId
{
get
{
var claims = ((ClaimsIdentity)Context.User.Identity).Claims.ToArray();
var userIdClaim = claims.FirstOrDefault(x => x.Type.Equals("UserId"));
if (userIdClaim == null) return Guid.Empty;
return Guid.Parse(userIdClaim.Value);
}
}
/// <summary>
/// Consructor
/// </summary>
/// <param name="lifetimeScope"></param>
/// <param name="context"></param>
public NotificationHub(ILifetimeScope lifetimeScope, IHubContext<INotificationHub> context)
{
GlobalContext = context;
try
{
var childScope = lifetimeScope.BeginLifetimeScope();
_chatMessagingService = childScope.Resolve<IChatMessagingService>();
_userService = childScope.Resolve<IUserService>();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
/// <summary>
/// Notifications
/// </summary>
public void Notifications()
{
Clients.All.pushNotification("AllyHr" + LoggedInUserId);
}
/// <summary>
/// Send Message
/// </summary>
/// <param name="model"></param>
public void SendMessage(SendChatMessageBindingModel model)
{
var chatMessage = _chatMessagingService.SendMessageToGroup(LoggedInUserId, model.GroupId, model.Message);
var recipientIds = _chatMessagingService.GetChatMembersByGroupId(LoggedInUserId, model.GroupId);
var stringUserIds = new List<string>();
var chatGroup = _chatMessagingService.GetChatGroupById(model.GroupId);
foreach (var recipientId in recipientIds)
{
stringUserIds.Add(recipientId.ToString());
}
Clients.Users(stringUserIds).getNotification(new
{
message = "A new Message is Recieved in Chat Group: " + chatGroup.Name,
groupId = chatGroup.Id
});
var chatMessageVm = chatMessage.Map<ChatMessage, ChatMessageViewModel>();
chatMessageVm.Sender = _userService.Get(chatMessageVm.SenderId).Map<User, UserViewModel>();
stringUserIds.Add(LoggedInUserId.ToString());
Clients.Users(stringUserIds).getMessage(chatMessageVm);
}
}
signalRhelper.cs use to call from api or from Hangfire services
public class SignalRHelper
{
public IConnectionManager ConnectionManager { get; set; }
public IHubContext<INotificationHub> HubContext { get; set; }
/// <summary>
/// Send Notifications to Users
/// </summary>
/// <param name="message"></param>
/// <param name="userIds"></param>
public void GetNotification(object message, IList<string> userIds)
{
HubContext.Clients.Users(userIds).getNotification(message);
}
/// <summary>
/// Get LoggedInUser Id for SignalR
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public static Guid GetLoggedInUserId(IPrincipal user)
{
var claim = GetLoggedinUserClaim(user);
if (claim == null) return Guid.Empty;
return Guid.Parse(claim.Value);
}
private static Claim GetLoggedinUserClaim(IPrincipal user)
{
var claim = ((ClaimsIdentity)user.Identity).Claims.ToArray();
return claim.FirstOrDefault(x => x.Type.Equals("UserId"));
}
}
Could this be related to Autofac creating a new lifetimescope for your call, but you were expecting to continue using the existing scope? Maybe check your autofac registrations for singleinstance / instanceperlifetimescope
Just saying, but have you registered any static classes? They can keep your scope alive for far too long.
I see you're using multiple containerbuilders - that's not something we do over here, we have one 'massive' containerbuilder for each app. I'm curious why you're doing that? To satisfy my curiosity, could you try using a single containerbuilder and registering everything on that single builder? (Although it looks like this is a pattern for SignalR and autofac)
The documentation says: " a common error in OWIN integration is the use of GlobalHost."
It looks like you're doing exactly that.

Simulate a delay in execution in Unit Test using Moq

I'm trying to test the following:
protected IHealthStatus VerifyMessage(ISubscriber destination)
{
var status = new HeartBeatStatus();
var task = new Task<CheckResult>(() =>
{
Console.WriteLine("VerifyMessage(Start): {0} - {1}", DateTime.Now, WarningTimeout);
Thread.Sleep(WarningTimeout - 500);
Console.WriteLine("VerifyMessage(Success): {0}", DateTime.Now);
if (CheckMessages(destination))
{
return CheckResult.Success;
}
Console.WriteLine("VerifyMessage(Pre-Warning): {0} - {1}", DateTime.Now, ErrorTimeout);
Thread.Sleep(ErrorTimeout - 500);
Console.WriteLine("VerifyMessage(Warning): {0}", DateTime.Now);
if (CheckMessages(destination))
{
return CheckResult.Warning;
}
return CheckResult.Error;
});
task.Start();
task.Wait();
status.Status = task.Result;
return status;
}
with the following unit test:
public void HeartBeat_Should_ReturnWarning_When_MockReturnsWarning()
{
// Arrange
var heartbeat = new SocketToSocketHeartbeat(_sourceSubscriber.Object, _destinationSubscriber.Object);
heartbeat.SetTaskConfiguration(this.ConfigurationHB1ToHB2_ValidConfiguration());
// Simulate the message being delayed to destination subscriber.
_destinationSubscriber.Setup(foo => foo.ReceivedMessages).Returns(DelayDelivery(3000, Message_HB1ToHB2()));
// Act
var healthStatus = heartbeat.Execute();
// Assert
Assert.AreEqual(CheckResult.Warning, healthStatus.Status);
}
Message_HB1ToHB2() just returns a string of characters and the "Delay Delivery" method is
private List<NcsMessage> DelayDelivery(int delay, string message)
{
var sent = DateTime.Now;
var msg = new NcsMessage()
{
SourceSubscriber = "HB1",
DestinationSubscriber = "HB2",
SentOrReceived = sent,
Message = message
};
var messages = new List<NcsMessage>();
messages.Add(msg);
Console.WriteLine("DelayDelivery: {0}", DateTime.Now);
Thread.Sleep(delay);
Console.WriteLine("DelayDelivery: {0}", DateTime.Now);
return messages;
}
I'm using Moq as the mocking framework and MSTest as the testing framework. Whenever I run the unit test, I get the following output:
DelayDelivery: 04/04/2013 15:50:33
DelayDelivery: 04/04/2013 15:50:36
VerifyMessage(Start): 04/04/2013 15:50:36 - 3000
VerifyMessage(Success): 04/04/2013 15:50:38
Beyond the obvious "code smell" using the Thread.Sleep in the methods above, the result of the unit test is not what I'm trying to accomplish.
Can anyone suggest a better/accurate way to use the Moq framework to simulate a delay in "delivery" of the message. I've left out some of the "glue" code and only included the relevant parts. Let me know if something I've left out that prevents you from being able to understand the question.
If you want a Moq mock to just sit and do nothing for a while you can use a callback:
Mock<IFoo> mockFoo = new Mock<IFoo>();
mockFoo.Setup(f => f.Bar())
.Callback(() => Thread.Sleep(1000))
.Returns("test");
string result = mockFoo.Object.Bar(); // will take 1 second to return
Assert.AreEqual("test", result);
I've tried that in LinqPad and if you adjust the Thread.Sleep() the execution time varies accordingly.
When you setup your mock you can tell the thread to sleep in the return func:
Mock<IMyService> myService = new Mock<IMyService>();
myService.Setup(x => x.GetResultDelayed()).Returns(() => {
Thread.Sleep(100);
return "result";
});
If running asynchronous code, Moq has the ability to delay the response with the second parameter via a TimeSpan
mockFooService
.Setup(m => m.GetFooAsync())
.ReturnsAsync(new Foo(), TimeSpan.FromMilliseconds(500)); // Delay return for 500 milliseconds.
If you need to specify a different delay each time the method is called, you can use .SetupSequence like
mockFooService
.SetupSequence(m => m.GetFooAsync())
.Returns(new Foo())
.Returns(Task.Run(async () =>
{
await Task.Delay(500) // Delay return for 500 milliseconds.
return new Foo();
})
I could not get Moq version to work, so I ended up making something like this:
a small example using WaitHandle:
[TestFixture]
public class EventWaitHandleTests
{
class Worker {
private volatile bool _shouldStop;
public EventWaitHandle WaitHandleExternal;
public void DoWork ()
{
while (!_shouldStop)
{
Console.WriteLine("worker thread: working...");
Thread.Sleep(1000);
WaitHandleExternal.Set();
}
}
public void RequestStop()
{
_shouldStop = true;
}
}
[Test]
public void WaitForHandleEventTest()
{
EventWaitHandle _waitHandle = new AutoResetEvent (false); // is signaled value change to true
// start a thread which will after a small time set an event
Worker workerObject = new Worker ();
workerObject.WaitHandleExternal = _waitHandle;
Thread workerThread = new Thread(workerObject.DoWork);
// Start the worker thread.
workerThread.Start();
Console.WriteLine ("Waiting...");
_waitHandle.WaitOne(); // Wait for notification
Console.WriteLine ("Notified");
// Stop the worker thread.
workerObject.RequestStop();
}
}
I like and voted for serup's solution. My answer is a version of his converted for use as a library.
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
/// <summary>
/// support halting a workflow and waiting for a finish request
/// </summary>
public class MockWorker
{
private readonly DateTime? _end;
private volatile bool _shouldStop;
/// <summary>
/// Create a worker object
/// </summary>
/// <param name="timeoutInMilliseconds">How long before DoWork will timeout. default - Null will not timeout.</param>
public MockWorker(int? timeoutInMilliseconds = null)
{
if (timeoutInMilliseconds.HasValue)
_end = DateTime.Now.AddMilliseconds(timeoutInMilliseconds.Value);
}
/// <summary>
/// Instruct DoWork to complete
/// </summary>
public void RequestStop()
{
_shouldStop = true;
}
/// <summary>
/// Do work async will run until either timeoutInMilliseconds is exceeded or RequestStop is called.
/// </summary>
public async Task DoWorkAsync()
{
while (!_shouldStop)
{
await Task.Delay(100);
if (_end.HasValue && _end.Value < DateTime.Now)
throw new AssertFailedException("Timeout");
}
}
/// <summary>
/// Do work async will run until either timeoutInMilliseconds is exceeded or RequestStop is called.
/// </summary>
/// <typeparam name="T">Type of value to return</typeparam>
/// <param name="valueToReturn">The value to be returned</param>
/// <returns>valueToReturn</returns>
public async Task<T> DoWorkAsync<T>(T valueToReturn)
{
await DoWorkAsync();
return valueToReturn;
}
}
I had a similiar situation, but with an Async method. What worked for me was to do the following:
mock_object.Setup(scheduler => scheduler.MakeJobAsync())
.Returns(Task.Run(()=> { Thread.Sleep(50000); return Guid.NewGuid().ToString(); }));

Categories