1

I have a code snippet to mock which is as follows inside a test method:

IManager imanager;

public classToBeTested(Manager manager)
{
  imanager=manager;
}

public void methodToBeTested()
{
  StringBuilder errorString = new StringBuilder();
  List<RuleWrapper> allRules;
  Dictionary<Guid, RuleConfiguration> allRuleConfigurations;
  imanager.LoadAllRules(ref errorString, out allRuleConfigurations, out allRules);
  SetGlobalRuleConfigurations(settingsManager, allRuleConfigurations, _globalRuleConfigurations);
}

How do I mock the behavior of mock by setting up out parameters:

I have the following snippet that I am trying to implement:

public void methodUsedForTesting()
{
  StringBuilder errorString = new StringBuilder();
  List<Rule> allRules = new List<Rule>();
  Dictionary<Guid, RuleConfiguration> allRuleConfigurations = new Dictionary<Guid,RuleConfiguration>();

  //Code for Initializing above Data members
  RuleConfiguration ruleConfiguration1 = new RuleConfiguration("First");
  RuleConfiguration ruleConfiguration2 = new RuleConfiguration("Second");
  allRuleConfigurations.Add(ruleConfiguration1);
  allRuleConfigurations.Add(ruleConfiguration2);

  Rule rule1 = new Rule("First");
  Rule rule2 = new Rule("Second");
  allRules.add(rule1);
  allRules.add(rule2);


  Mock<Manager> mockManager = new Mock<Manager>();
  mockManager.Setup(p => p.LoadAllRules(ref errorString, out allRuleConfigurations, out allRules));


  classToBeTested classtest = new classToBeTested(mockManager.Object);
  classtest.methodToBeTested();
}

What should be done so that the mock returns the data initialized by me and not the original behavior of that particular method?

jltrem
  • 12,124
  • 4
  • 40
  • 50
Deepak Kataria
  • 89
  • 2
  • 11
  • Are you asking how to set the values of the out parameters? Moq won't set them for you; you just set them to what you expect them to be. See [here](http://stackoverflow.com/q/25206450/1698557). – Patrick Quirk Aug 08 '14 at 19:54
  • Can you post a more complete code example? Out and ref parameters are tricky business but they can be done (just need to see a bit more code). The other thing I would mention, is that two out parameters and one ref parameter seems like fishy business to me and generally is a code smell that indicates a bigger issue. Why not create a single class to wrap all three inputs, and return the new class from a function (instead of the method you have) without modifying the input parameters? – xDaevax Aug 08 '14 at 20:49
  • The Client code cannot be modified. I need to write the Unit tests so that the method is tested and all dependencies are mocked, so in the above code, I am trying to reduce the dependency by mocking the call to LoadAllRules Method. I am expecting that the LoadAllRules Method returns the values of errorstring, allRuleConfigurations and allRules to what I have initialized. – Deepak Kataria Aug 11 '14 at 12:54
  • You're setup is 100% correct. As written however, the ref parameters are not injected into into your `methodToBeTested()` and as a result, you cannot create a setup that will match the call being made to the manager. I would consider refactoring your code so that either a) `methodToBeTested()` has an overload that accepts arguments (that you can setup in your test and pass in) or b) make the `LoadAllRules` method return a value containing what you need instead of all those out and ref params (then the setup will be in what you mock the manager to return, not the inputs). – xDaevax Aug 13 '14 at 16:59
  • If you are unable to modify the original code, then you are in for some fun work. In my experience, returning multiple values via out parameters is a BAD PRACTICE and should be refactored as it forces object creation from within the method and tends to tie dependencies in to the method (causing the issues you face). You could try the solution metntioned here: http://stackoverflow.com/a/19598345/937012 to see if that helps but it will be *interesting*. – xDaevax Aug 13 '14 at 17:22

1 Answers1

1

To mock LoadAllRules it will need to be virtual or you need to be mocking an interface. Setting up your out values is not the problem, and I don't think it is providing the 'original behavior' of the method ... I think you are just not getting any values because the Setup is not finding a match. It isn't finding a match because of the ref parameter. Moq doesn't support an It.Any match on ref ... you have to provide the same object instance in order to match. See my example below. It configures the ref and different out values. The test is using xUnit.net with Moq.

    public interface IFoo
    {
        void Bar(ref StringBuilder err, out string val1, out string val2);
    }

    public class Foo : IFoo
    {
        public void Bar(ref StringBuilder err, out string val1, out string val2)
        {
            val1 = "Hello";
            val2 = "Howdy";
        }
    }

    public class ClassToBeTested
    {
        private IFoo _foo;

        public StringBuilder Errors = new StringBuilder();

        public ClassToBeTested(IFoo foo)
        {
            _foo = foo;
        }

        public string MethodToBeTested()
        {
            string val1, val2;
            _foo.Bar(ref Errors, out val1, out val2);
            return val1 + " " + val2;
        }
    }

    [Fact]
    public void MethodUsedForTesting()
    {
        var foo = new Mock<IFoo>();
        var sut = new ClassToBeTested(foo.Object);

        var val1 = "Hi";
        var val2 = "Hey";

        // the setup for a ref has to be the same object instance 
        // that is used by MethodToBeTested() ...
        // there isn't currently It.Any support for ref parameters
        foo.Setup(x => x.Bar(ref sut.Errors, out val1, out val2));

        string expected = "Hi Hey";
        string actual = sut.MethodToBeTested();

        Assert.Equal(expected, actual); // this test passes
    }

This works because the sut.Errors is the same object as is used inside of MethodToBeTested. In your sample code this is not the case ... there are two different objects so there will not be a match.

Adding tests to a legacy system is always problematic. Moq can't handle this today. You might take a look at RhinoMocks which appears to be able to handle ref args.

Community
  • 1
  • 1
jltrem
  • 12,124
  • 4
  • 40
  • 50
  • Yes, I have Initialized the out variables before the use of setup method but still it doesn't respond with the initialized data. – Deepak Kataria Aug 11 '14 at 16:26
  • @DeepakKataria The example I provided does provide the values initialized by setup. The asserts pass. Keep in mind the lazy evaluation due to the lamba ... e.g., if `val1` is assigned a different value before `foo.Object.Bar` is called, then the out value will receive this latest value. You'll need to post more code if you need help locating the specific problem you are encountering. – jltrem Aug 11 '14 at 16:32
  • @DeepakKataria don't avoid lazy evaluation ... work with it. When calling Setup for your mock give it objects that won't be changed by other code. – jltrem Aug 12 '14 at 13:21
  • I am not allowed to touch the code base, so anything that can be done with this situation. – Deepak Kataria Aug 12 '14 at 13:49
  • @DeepakKataria I'm talking about making changes to methodUsedForTesting, which you are writing, and which you'll run in your unit testing framework. I can't say any more without seeing your full test method. – jltrem Aug 12 '14 at 13:57
  • The method used for testing is the same in above code. The three parameters that are passed to mock are just initialized before the call. – Deepak Kataria Aug 12 '14 at 14:03
  • @DeepakKataria but your test method never calls `mockManager.Object.LoadAllRules` after it does the `Setup`. How can you say that it is returning the "original data"? Are you actually calling `methodToBeTested` and expecting it to use your mock object? That won't happen. Unless `manager` can be replaced by dependency injection (viz., that the field is an interface) then you can't mock it. If this is the case, then the original code was not designed for testability. – jltrem Aug 12 '14 at 14:11
  • I have done the following things: Using the test class constructor, I have passed the mocked object for Manager using dependency injection so that the calls are placed on mock itself. Yes, using the test class reference I am calling methodToBeTested and the call is being placed on mock because of dependency injection, its just that the values being used are locals that are the reference as can be seen in the code below. – Deepak Kataria Aug 12 '14 at 15:09
  • @DeepakKataria Edit your question and provide all the pertinent code. Otherwise we can only guess what you are really doing. – jltrem Aug 12 '14 at 15:17
  • @DeepakKataria updated my question in response to your new code. The problem is that we aren't matching due to the ref parameter – jltrem Aug 13 '14 at 15:32