7

As there is no RelayCommandAsync (at least not that I know of), how to test this scenario. For example:

public RelayCommand LoadJobCommand
{
    get
    {
        return this.loadJobCommand ?? (
            this.loadJobCommand =
            new RelayCommand(
                this.ExecuteLoadJobCommandAsync));
    }
}

private async void ExecuteLoadJobCommandAsync()
{
   await GetData(...);
}

Test:

vm.LoadJobCommand.Execute()

Assert.IsTrue(vm.Jobs.Count > 0)
O.O
  • 11,077
  • 18
  • 94
  • 182
  • 1
    MSDN magazine [posted a article](https://msdn.microsoft.com/en-us/magazine/dn630647.aspx) showing a `AsyncCommand` (basically a implementation of a `RelayCommandAsync`), it provides a `public async Task ExecuteAsync(object parameter)` you could await on. – Scott Chamberlain Mar 18 '15 at 21:43
  • @ScottChamberlain - what does that link have to do with mvvm-light and RelayCommands? – O.O Mar 18 '15 at 21:46
  • It doesn't. mvvm-light does not have what you need so you need to write your own async relay command (if you want the unit test to be able to call the command). That article gives you a implementation. Because this is not a solution inside mvvm-light I postead as a comment instead of an answer because I felt it did not "answer your question" but did give you another option. – Scott Chamberlain Mar 18 '15 at 21:47
  • @ScottChamberlain - understood, I will try to inquire with the mvvm-light team first to see if they have an async RelayCommand in the works and use InternalsVisableTo for the time being, unless someone answers a better solution below. – O.O Mar 18 '15 at 21:56

3 Answers3

1

Why don't you cover GetData(...) method with tests? I don't see any sense in testing relay commands

rum
  • 222
  • 1
  • 12
  • 2
    It's not testing RelayCommands, it's testing private methods that the RelayCommand calls. – O.O Mar 18 '15 at 21:44
  • you are mssing the point. The viewmodel is being triggered by the relaycommand and as such you need to test if that trigger is properly handled. – Philip Stuyck Mar 18 '15 at 21:44
  • 1
    @O.O A way to implement it is make the method `internal` then use [`InternalsVisableTo`](https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute(v=vs.110).aspx) to allow the unit test access to the method – Scott Chamberlain Mar 18 '15 at 21:45
  • @PhilipStuyck but isn't it ui tests' buisness? i thought unit tests are for model only – rum Mar 18 '15 at 21:47
  • @ScottChamberlain - That makes sense, I might give that a go. – O.O Mar 18 '15 at 21:48
  • and the relaycommand is part of the viewmodel, not the view, the view simply binds to the viewmodel, and you test the viewmodel. – Philip Stuyck Mar 18 '15 at 21:49
  • use internalsvisableto is for me really the last resort, but I can understand the pragmatic approach. – Philip Stuyck Mar 18 '15 at 21:49
  • @PhilipStuyck - I agree, biggest downside for me is that it will hurt my code coverage percentage, but it's more important that what I need tested is tested in the end. – O.O Mar 18 '15 at 21:54
1

I was not using async/await but I have run in to a similar problem in the past. The situation I was in is the method called a Task.Run( inside of itself and the unit test was verifying that the ViewModel was calling the service with the correct number of times with the correct parameters.

The way we solved this was we had our Mock of the service that was being called use a ManualResetEventSlim, then the unit test waited for that reset event to be called before proceeding.

[TestMethod]
public void EXAMPLE()
{
    using (var container = new UnityAutoMoqContainer())
    {
        //(SNIP)

        var serviceMock = container.GetMock<ITreatmentPlanService>();

        var resetEvent = new ManualResetEventSlim();

        serviceMock.Setup(x=>x.GetSinglePatientViewTable(dateWindow, currentPatient, false))
            .Returns(() =>
            {
                resetEvent.Set();
                return new ObservableCollection<SinglePatientViewDataRow>();
            });

        var viewModel = container.Resolve<SinglePatientViewModel>();

        //(SNIP)

        viewModel.PatientsHadTPClosed(guids, Guid.NewGuid());

        waited = resetEvent.Wait(timeout);
        if(!waited)
            Assert.Fail("GetSinglePatientViewTable was not called within the timeout of {0} ms", timeout);

        //(SNIP)

        serviceMock.Verify(x => x.GetSinglePatientViewTable(dateWindow, currentPatient, false), Times.Once);
    }
}

If this approach works or not for you all depends on what your unit test is actually testing. Because you check Assert.IsTrue(vm.Jobs.Count > 0) it looks like you have extra logic that is being done after the await GetData(...); call, so this might not be applicable for your current problem. However, this may be helpful for other unit tests you need to write for your view model.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • mocking is not an option, unfortunately, (windows store app), I'm still looking into the async relaycommand you commented about earlier. – O.O Mar 19 '15 at 15:06
1

It really depends on what you are trying to test:

  1. Test that the RelayCommand is properly hooked up and calls your async method?

or

  1. Test that the Async Method logic is correct?

1. Testing the RelayCommand trigger

1.a Using External Dependencies to verify

From my personal experience the easiest way to test that the trigger is wired up correctly to execute the command and then test that your class has interacted with another external class somewhere as expected. E.g.

private async void ExecuteLoadJobCommandAsync()
{
   await GetData(...);
}

private async void GetData(...)
{
   var data = await _repo.GetData();
   Jobs.Add(data);
}

Its fairly easy to test that your repo gets called.

    public void TestUsingExternalDependency()
    {
        _repo.Setup(r => r.GetData())
            .Returns(Task.Run(() => 5))
            .Verifiable();

        _vm.LoadJobCommand.Execute(null);

        _repo.VerifyAll();
    }

I sometimes even do this, so that it doesn't try to process everything:

    [Test]
    public void TestUsingExternalDependency()
    {
        _repo.Setup(r => r.GetData())
            .Returns(() => { throw new Exception("TEST"); })
            .Verifiable();

        try
        {
            _vm.LoadJobCommand.Execute(null);
        }
        catch (Exception e)
        {
            e.Message.Should().Be("TEST");
        }

        _repo.VerifyAll();
    }

1.b Using a Scheduler

Another option is to use a scheduler, and schedule tasks using that.

public interface IScheduler
{
    void Execute(Action action);
}

// Injected when not under test
public class ThreadPoolScheduler : IScheduler
{
    public void Execute(Action action)
    {
        Task.Run(action);
    }
}

// Used for testing
public class ImmediateScheduler : IScheduler
{
    public void Execute(Action action)
    {
        action();
    }
}

Then in your ViewModel

    public ViewModelUnderTest(IRepository repo, IScheduler scheduler)
    {
        _repo = repo;
        _scheduler = scheduler;
        LoadJobCommand = new RelayCommand(ExecuteLoadJobCommandAsync);
    }
    private void ExecuteLoadJobCommandAsync()
    {
        _scheduler.Execute(GetData);

    }

    private void GetData()
    {
        var a =  _repo.GetData().Result;
        Jobs.Add(a);
    }

And your test

    [Test]
    public void TestUsingScheduler()
    {
        _repo.Setup(r => r.GetData()).Returns(Task.Run(() => 2));

        _vm = new ViewModelUnderTest(_repo.Object, new ImmediateScheduler());

        _vm.LoadJobCommand.Execute(null);

        _vm.Jobs.Should().NotBeEmpty();
    }

2. Testing the GetData Logic

If you are looking to test get GetData() logic or even the ExecuteLoadJobCommandAsync() logic. Then you should definitely make the method you want to test, as Internal, and mark your assmebly as InternalsVisibleTo so that you can call those methods directly from your test class.

Michal Ciechan
  • 13,492
  • 11
  • 76
  • 118
  • 1
    "It really depends on what you are trying to test" was the point I was trying to get across in my answer but you did it in a much better way :) – Scott Chamberlain Mar 19 '15 at 13:55
  • @Michal - this is good, and I didn't mention it, but I am writing a windows store app and unless something has changed, mocking frameworks do not work in windows store apps due to the changes in some of the reflection stuff. So still looks like InternalsVisibleTo – O.O Mar 19 '15 at 15:04
  • @O.O will try it out, i would be very surprised if there isn't any mocking frameworks that work on windows store apps target. – Michal Ciechan Mar 19 '15 at 15:15
  • @O.O There is a [experimental branch of Moq](https://github.com/mbrit/moqrt) that works for WinRT (Windows store) apps. You may also be interested in other answers given in this SO question: [What is the mocking framework of choice in Unit Test Library for Windows Store Applications?](http://stackoverflow.com/questions/12083462) – Scott Chamberlain Mar 19 '15 at 17:28
  • @MichalCiechan - Going to go the scheduler route. – O.O Aug 26 '15 at 21:43
  • Why is this marked as the correct answer? In your example, VerifyAll won't wait for command execution. You can check it by setting Task.Delay in command. – WhoKnows Nov 29 '22 at 10:34
  • The first option doesn't test that the job was added indeed, just tests that the `_repo.GetData` was called. But the immediate scheduler solution would work as long as you always use the immediate scheduler, including for `Task.Delay(...)`, which would probably look like `_scheduler.Delay(...)`. For more complex projects, I ended up implementing a custom WpfDispatcher that records any outstanding tasks, so that you can wait for them to complete in between steps if needed. – Michal Ciechan Dec 13 '22 at 13:48