0

Not sure how to achieve this. I'm trying to unit test a method which waits a few minutes, see here:

internal class JctRestartViaSmsAttemptTestRunner : ITestRunner<JctRestartViaSmsAttempt>
{
    private readonly IMayWantMonitoring _queues;
    private readonly IAppSettings _appSettings;

    public JctRestartViaSmsAttemptTestRunner(IMayWantMonitoring queues, IAppSettings appSettings)
    {
        _queues = queues;
        _appSettings = appSettings;
    }

    public JctTest Execute(JctRestartViaSmsAttempt jctMessageType)
    {
        // after five minutes, publish an event to check if the JCT logged in
        var jctLoggedInTimeOut = TimeSpan.FromMinutes(double.Parse(_appSettings["JctLogInTimeOut"]));
        var message = new JctRestartViaSmsValidate(jctMessageType.Imei);

        Task.Delay(jctLoggedInTimeOut)
            .ContinueWith(x => _queues.Publish(message));

        // reset test values
        return new JctTest("6", jctMessageType.Imei, null, null, null);
    }
}

This is my test method but I can't manage to mock the task delay stuff.

    [Test]
    public void TaskTest()
    {
        // arrange
        var imei = _fixture.Create<string>();
        _appSettings.Setup(c => c["JctLogInTimeOut"]).Returns("5");
        var message = _fixture.Build<JctRestartViaSmsAttempt>()
            .With(x => x.Imei, imei)
            .Create();

        var sut = _fixture.Create<JctRestartViaSmsAttemptTestRunner>();

        // act
        sut.Execute(message);

        // assert
        _queues.Verify(x => x.Publish(It.Is<JctRestartViaSmsValidate>(y => y.Imei == imei)));
    }

This is the error raised:

Moq.MockException : Expected invocation on the mock at least once, but was never performed: x => x.Publish(It.Is(y => y.Imei == .imei)) No setups configured. No invocations performed. at Moq.Mock.ThrowVerifyException(MethodCall expected, IEnumerable1 setups, IEnumerable1 actualCalls, Expression expression, Times times, Int32 callCount) at Moq.Mock.VerifyCalls(Interceptor targetInterceptor, MethodCall expected, Expression expression, Times times) at Moq.Mock.Verify[T](Mock1 mock, Expression1 expression, Times times, String failMessage) at Moq.Mock1.Verify(Expression1 expression) at JustEat.PrivateAPN.Worker.UnitTests.TestRunners.JctRestartViaSmsAttemptTestFixture.RunJctTest_WhenRestartAttemptSmsIsSent_ShouldPublishJctRestartValidateMessageWithTheRightImei() in

I know I need to customize/configure my fixture in order to get into the callback but I'm not sure how to do it, any help would be very appreciated

Rober
  • 726
  • 8
  • 27
  • I want to verify the call of _queues.Publish(message) – Rober Jun 27 '16 at 09:59
  • `JctRestartViaSmsValidate != JctRestartViaSmsAttempt` – Callum Linington Jun 27 '16 at 10:00
  • `var message = new JctRestartViaSmsValidate(jctMessageType.Imei);` is what is put in the `_queues` and `_queues.Verify(x => x.Publish(It.Is(y => y.Imei == imei)));` is what you verify is going onto the queue? Even if this doesn't solve your problem, it still looks wrong to me – Callum Linington Jun 27 '16 at 10:02
  • That's right, I just update the question. The error is still the same. – Rober Jun 27 '16 at 10:04
  • the `_queues` mock, how is that being injected into the `JctRestartViaSmsAttemptTestRunner` class? – Jamie Rees Jun 27 '16 at 10:14
  • I updated the snippet – Rober Jun 27 '16 at 10:17
  • 1
    `Task.Delay` is completely the wrong thing to be using here. You may want to try `Thread.Sleep`. If you want the non blocking version, you need to make your function `async` and `await` the Task.Delay – Callum Linington Jun 27 '16 at 10:57

3 Answers3

1

You shouldn't be firing a Task off without awaiting for it to finish when your test and other things may rely on the result.

So I suggest you change to:

internal class JctRestartViaSmsAttemptTestRunner : ITestRunner<JctRestartViaSmsAttempt>
{
    private readonly IMayWantMonitoring _queues;
    private readonly IAppSettings _appSettings;

    public JctRestartViaSmsAttemptTestRunner(IMayWantMonitoring queues, IAppSettings appSettings)
    {
        _queues = queues;
        _appSettings = appSettings;
    }

    public async Task<JctTest> ExecuteAsync(JctRestartViaSmsAttempt jctMessageType)
    {
        // after five minutes, publish an event to check if the JCT logged in
        var jctLoggedInTimeOut = TimeSpan.FromMinutes(double.Parse(_appSettings["JctLogInTimeOut"]));
        var message = new JctRestartViaSmsValidate(jctMessageType.Imei);

        // this will now delay in a non blocking fashion.
        await Task.Delay(jctLoggedInTimeOut);

        _queues.Publish(message);

        // reset test values
        return new JctTest("6", jctMessageType.Imei, null, null, null);
    }
}

Another reason for awaiting is that you have this _queues if you intend to read from that later on, you can never guarantee the contents because there may still be a thread in the threadpool processing the Task.Delay.

Alternative

If you can't change the signature of the method then you will have to go with the Thread.Sleep() which will block the current thread until it has finished.

Because you did specify Task.Delay I would assume you are using it for the benefits of non blocking.

If you were to use Thread.Sleep() you may want to consider running the JctRestartViaSmsAttemptTestRunner in a Task.Run() so that is will only block the thread it is running on.

internal class JctRestartViaSmsAttemptTestRunner : ITestRunner<JctRestartViaSmsAttempt>
{
    private readonly IMayWantMonitoring _queues;
    private readonly IAppSettings _appSettings;

    public JctRestartViaSmsAttemptTestRunner(IMayWantMonitoring queues, IAppSettings appSettings)
    {
        _queues = queues;
        _appSettings = appSettings;
    }

    public JctTest Execute(JctRestartViaSmsAttempt jctMessageType)
    {
        // after five minutes, publish an event to check if the JCT logged in
        var jctLoggedInTimeOut = TimeSpan.FromMinutes(double.Parse(_appSettings["JctLogInTimeOut"]));
        var message = new JctRestartViaSmsValidate(jctMessageType.Imei);

        Thread.Wait(jctLoggedInTimeOut.Milliseconds);

        _queues.Publish(message);

        // reset test values
        return new JctTest("6", jctMessageType.Imei, null, null, null);
    }
}

Tests

If you have a method that does return Task<> then you will have to use .Result if you can't have async test method signatures. Which shouldn't be an issue if you run all your tests in serial. If you don't know why .Result and .Wait() are bad read here

So for your async version:

JctTest test = runner.ExecuteAsync().Result;

And your non async version stays the same.

Callum Linington
  • 14,213
  • 12
  • 75
  • 154
  • When using await, you don't need ContinueWith. – JanDotNet Jun 27 '16 at 11:01
  • Yep, just changed it – Callum Linington Jun 27 '16 at 11:02
  • If `Execute` is not part of the interface and it is possible to change the method signature, this solution is the way to go. Note that you have to call `Execute(...).Wait()` in the unit test. – JanDotNet Jun 27 '16 at 11:04
  • I'll put the alternative in – Callum Linington Jun 27 '16 at 11:04
  • Consider to rename `Execute` to `ExecuteAsync` following the .Net naming conventions. – JanDotNet Jun 27 '16 at 11:05
  • Execute is part of the interface. Should I create a separate method? – Rober Jun 27 '16 at 11:06
  • It depends on the call chain to the method, If you have control over how all these are called then by all means change the signature or add the `Async` version, which is what most people do to preserve backward compatibility. – Callum Linington Jun 27 '16 at 11:08
  • Looking good but the publish should happen only after the delay. I might go with the "ContinueWith()" approach – Rober Jun 27 '16 at 11:21
  • They both do though??? In my examples they do exactly what you want.... – Callum Linington Jun 27 '16 at 11:31
  • I think you may not understand the `await` keyword? It means that whilst the `Task.Delay` or any `Task` is executing don't proceed with this code until it has finished. But because we `await`, the thread that the execute function was called on won't block until the `Task` has finished. This doesn't mean that the code in this method will continue on executing though... Non Blocking means that if the UI thread was the one that called this method or it was a ASP.NET request thread, those threads can continue doing the work they need to whilst your code is hanging around. – Callum Linington Jun 27 '16 at 11:34
  • Thank you for your explanation @CallumLinington it was very helpful :) – Rober Jun 27 '16 at 11:56
0

The execute method is not realy testable, because it starts a delayed task, but you are not able to manage that task from outside.

So, you could try to work with Thread.Sleep which is not nice but should work:

[Test]
public void TaskTest()
{
    // arrange
    var imei = _fixture.Create<string>();
    _appSettings.Setup(c => c["JctLogInTimeOut"]).Returns("1");
    var message = _fixture.Build<JctRestartViaSmsAttempt>()
        .With(x => x.Imei, imei)
        .Create();

    var sut = _fixture.Create<JctRestartViaSmsAttemptTestRunner>();

    // act
    sut.Execute(message);

    // verify the task was not executed yet
    // ....

    // let the test thread sleep and give the task some time to run
    Thread.Sleep(2000);

    // assert
    _queues.Verify(x => x.Publish(It.Is<JctRestartViaSmsValidate>(y => y.Imei == imei)));
}
JanDotNet
  • 3,746
  • 21
  • 30
0

I think the problem is the test is allowed to finish. Your creating a new task after the delay to start but the test function itself completes before this.

I am not overly familiar with the testing framework. But I would expect you want to use .Wait() on the end of the task. This will create a blocking task and your main test thread will be blocked until the delay is finished.

  • 1
    No, never use `.Wait()`. Use `await Task.Delay` instead – Callum Linington Jun 27 '16 at 10:55
  • You might not be able to use await in this test framework. If you can use await then that would be better of course. Otherwise the blocking would be unavoidable as far as I can see. The function isn't decorated with async. –  Jun 27 '16 at 10:57
  • Ahh, sorry it was unclear where you wanted to be calling `.Wait()`. You would have to in the test, which is fine because you shouldn't hit the problem of deadlocks. But, his current method signature doesn't expose a return type of `Task<>` so you couldn't `.Wait()` it – Callum Linington Jun 27 '16 at 11:11