PlTaylor
PlTaylor

Reputation: 7525

Is there a better way to test this MVVM Light Message that doesn't cause an occasional threading error?

I have a property in my viewmodel whose sole job is to send out a message when the property is changed. I wanted to unit test that it was indeed doing it's job so I wrote the following test

[TestFixture, RequiresSTA]
public class ToolBarViewModelTests
{
    private SecondaryScalingMode _SecondaryScalingMode;

    private void RecordSecondaryScalingSetting(SecondaryScalingMode obj)
    {
        _SecondaryScalingMode = obj;
    }

    [Test]
    public void AssertCombineAllCornersScalingMessageIsSent()
    {
        var vm = CreateNewToolBar();

        _SecondaryScalingMode = SecondaryScalingMode.IndividualCorner;

        AppMessages.SetSecondaryScalingMode.Register(this, (Action<SecondaryScalingMode>)RecordSecondaryScalingSetting);

        vm.CombineAllCornersScaling = true;
        Assert.IsFalse(vm.IndividualCornerScaling);
        Assert.IsFalse(vm.LeftRightScaling);
        Assert.IsFalse(vm.FrontRearScaling);
        Assert.IsTrue(vm.CombineAllCornersScaling);
        Assert.AreEqual(SecondaryScalingMode.CombineAllCorners, _SecondaryScalingMode);
    }
}

This test asserts the correct behavior, but it occassionally fails both locally and on my build server due to a threading error. The error is as follows

Test(s) failed. System.Reflection.TargetInvocationException : Exception has been thrown         by the target of an invocation.  
----> System.InvalidOperationException : The calling thread cannot access this object because a different thread owns it.  
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)

Is there a better way I can write this test that would prevent it from failing randomly in this way?

Upvotes: 2

Views: 334

Answers (1)

silverfighter
silverfighter

Reputation: 6882

OK this is the approach I usually take. This is pretty close to what you do. Keep in Mind you can also highjack the Messenger and subscribe to the Messenger In the unit test.

I posted a simplified version of your code to match to example.

I used to have also some occansionly strange errors in my unit tests and it was a timing/thread issue...

for that it helped to reset the Messenger foreach Unit test...

This is the viewmodel (simplefied guess work =) ... ):

public class UnitTestViewModel:ViewModelBase
{
    public UnitTestViewModel()
    {
        Messenger.Default.Register<string>(this, "SetSecondaryScalingMode", (message) =>
        {
            UpdateScalingMode();
        });
    }

    private bool _CombineAllCornersScaling;
    public bool CombineAllCornersScaling
    {
        get {
            return _CombineAllCornersScaling;
        }
        set {
            _CombineAllCornersScaling = value;
            Messenger.Default.Send<string>("update", "SetSecondaryScalingMode");
            RaisePropertyChanged("CombineAllCornersScaling");
        }
    }

    private bool _IndividualCornerScaling;
    public bool IndividualCornerScaling
    {
        get
        {
            return _IndividualCornerScaling;
        }
        set
        {
            _IndividualCornerScaling = value;

            RaisePropertyChanged("IndividualCornerScaling");
        }
    }

    private bool _LeftRightScaling;
    public bool LeftRightScaling
    {
        get
        {
            return _LeftRightScaling;
        }
        set
        {
            _LeftRightScaling = value;

            RaisePropertyChanged("LeftRightScaling");
        }
    }

    private bool _FrontRearScaling;
    public bool FrontRearScaling
    {
        get
        {
            return _FrontRearScaling;
        }
        set
        {
            _FrontRearScaling = value;

            RaisePropertyChanged("FrontRearScaling");
        }
    }

    private void UpdateScalingMode()
    {
        IndividualCornerScaling = false;
        LeftRightScaling = false;
        FrontRearScaling = true;
    }
}

this is the Unit test (using MSTest but could apply to other packages)...

 [TestMethod]
   public void UpdaetScalingMode()
   {
       Messenger.Reset();
       var vm = new UnitTestViewModel();
       bool updateMessageWasSend = false;
       string _thisCanBeTheObjectYouWerePassing = String.Empty;
       Messenger.Default.Register<string>(vm, "SetSecondaryScalingMode", (msg) =>
       {
           _thisCanBeTheObjectYouWerePassing = msg;
           updateMessageWasSend = true;
       });

       vm.CombineAllCornersScaling = true;

       Assert.AreEqual(true, updateMessageWasSend);
       Assert.AreEqual(false, vm.IndividualCornerScaling);
       Assert.AreEqual(false, vm.LeftRightScaling);
       Assert.AreEqual(true, vm.CombineAllCornersScaling);
       Assert.AreEqual("update", _thisCanBeTheObjectYouWerePassing);

   }

Rember this is simplyfied to show the concept.

I did just passed a string for message payload but this can be as complex as you want with all messenger functionalities....

I hope this helps... and otherwise it would be helpful to see the ViewModel code to cum up with another test strategy ...

P.S.: I also developed the habit to have 1 assertion per Test only and write more tests instead...

Upvotes: 1

Related Questions