chevron-thin-right chevron-thin-left brand cancel-circle search youtube-icon google-plus-icon linkedin-icon facebook-icon twitter-icon toolbox download check linkedin phone twitter-old google-plus facebook profile-male chat calendar profile-male
0 votes
Hi, I would like to know how to specify that calls to a method were made given certain specific arguments and to subsequently mark any other method combinations as a test failure.

For example, I am creating a WPF client using Prism. There are three modules to this application, each of which implement IModule and register types with the IUnityContainer in the IModule.Initialize() implementation.

Taking one module in particular - the UsersModule - this module should call UnityContainerExtensions.RegisterType<object, UsersDocumentPane>(fakeUnityContainer, "UsersDocumentPane");

However, any other calls to any overload of RegisterType() should be considered a test failure.

Is there a way to enforce this succinctly?
asked by gurhall (2.3k points)

4 Answers

0 votes
Hi,

You can do that using the DoInstead API.
In the example below the class Foo has two overloads of method Bar.
Here I want the test to fail if Bar(string) gets called.
public class Foo
{
    public int Bar(int x)
    {
        return 1;
    }

    public int Bar(string s)
    {
        return 2;
    }
}

[Test]
public void TestWasNotCalledWithArguments()
{
    var fake = Isolate.Fake.Instance<Foo>();

    Isolate.WhenCalled(() => fake.Bar("")).DoInstead(callContext =>
                                                         {
                                                             Type type = callContext.Parameters[0].GetType();
                                                             if (type == typeof (string))
                                                             {
                                                                 throw new Exception("Bar(string) should not have been called.");
                                                             }
                                                             return 5;
                                                         });

     fake.Bar(1);
    fake.Bar("a");
} 


Note that I failed the test by checking the argument type. You can also check the actual value of the argument like this:
string s = callContext.Parameters[0] as string;

Please let me know if it helps.
answered by ohad (35.4k points)
0 votes
Yes, it does help, but there a couple of issues with it. Firstly, it's extremely verbose and a little ugly. The following code prevents spurious calls to IRegionManager.RegisterViewWithRegion - but only for the overload for (string regionName, Type viewType).

Isolate.WhenCalled(() => RegionManagerExtensions.RegisterViewWithRegion(regionManager, "RegionName", typeof(object))).DoInstead(
                callContext =>
                {
                    var providedRegionManager = callContext.Parameters[0] as IRegionManager;
                    var providedRegionName = callContext.Parameters[1] as string;
                    var providedViewType = callContext.Parameters[2] as Type;
                    if(providedRegionManager != regionManager || 
                        (providedRegionName != "OutlookBarRegion" || providedRegionName != "RibbonRegion") ||
                        (providedViewType != typeof(TOutlookSection) || providedViewType != typeof(TRibbonContextualTabGroup)))
                    {
                        throw new InvalidOperationException(string.Format("RegisterViewWithRegion called with wrong arguments: regionName = `{0}`, viewType = `{1}`", providedRegionName, providedViewType.Name));
                    }
                    return regionManager;
                }
            );


Quite verbose really. Also, the next example I have is for registering a type with the IUnityContainer:

unitContainer.RegisterType&lt;object, TDocumentPane&gt;(moduleDocumentPaneName);


Trying to disallow all other permutations of calls using the example you gave is not realistic: in the Action or Func<> provided to WhenCalled(), I need to specify concrete types for the generic arguments, not just placeholders that I can then query using callContext.

I suppose this is one case where expect/record/replay semantics are actually useful.
answered by gurhall (2.3k points)
0 votes
Hi,

While Ohad's gave the correct way to do this, it may be possible that it's a case of testing too much.

Usually tests make sure things happened. If the thing didn't happen they fail. Obviously, you can do this the other way around: You can check that things didn't happen, then if they did, fail the test.

While this is ok - there's a catch. What are the chances of things failing and breaking your test for the wrong reason? The second case has a bigger chance of that.

When tests fail, we need to fix them or the bug. If they fail for the wrong reason, it's just manual work that shouldn't have been there in the first place.

My point is this - you should focus on testing things that have occurred. These tests have lesser chance of breaking and making you fix them. The kind the test you're writing here is of the second type. Obviously there are exceptions, but as a guideline, you'd better off sticking to that.

I hope that makes it clearer. This kind of thinking, by the way, goes into the APIs - we don't put in things that can lead you to more work.
answered by gilz (14.5k points)
0 votes
I agree that, most often, this is overkill. However, in this case (registering views and regions with unity and prism) only one specific set of parameters are expected and, therefore, allowed. If another developer goes into this code and starts registering other things, perhaps even overriding what is already registered in a particular region, then the tests will not currently catch this.

If the tests don't fail then the coder can 'get away with' these breaking changes. A failing test is akin to an omniscient code review, whereas giving them a green light even though the intent of the code has been circumvented, is a smell.
answered by gurhall (2.3k points)
...