Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test RelayCommand that Executes an async method?

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)
like image 522
O.O Avatar asked Mar 18 '15 21:03

O.O


2 Answers

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.

like image 76
Michal Ciechan Avatar answered Oct 01 '22 20:10

Michal Ciechan


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

like image 25
rum Avatar answered Oct 01 '22 19:10

rum