Rhino.Mocks and ref parameter

2019-06-23 17:55发布

问题:

I'm having problems to test a method that has a ref parameter. I'm not the library/code owner, so I can't change it, so please do not suggest that I remove the ref parameter.

I'm using this website as reference: http://ayende.com/wiki/Rhino%20Mocks%203.5.ashx#OutandRefarguments

Here is the test:

    [Test]
    public void TestBuildSimpleProfile()
    {
        // arrange
        var barMock = MockRepository.GenerateStrictMock<ICommandBar>();
        var controlBuilder = new ControlBuilder(barMock);

        var user = new Usuario();
        user.PRF_PERFIS = new Perfis();

        var perfil = new Perfil();
        perfil.FNC_FUNCIONALIDADES = new Funcionalidades();
        var func1 = new Funcionalidade();
        func1.FNC_NU_ID = 1;
        func1.FNC_FL_ATIVO = true;
        func1.FNC_NO_CHAVE = "Associar Registros";
        func1.FNC_DE_FUNCIONALIDADE = "{0653aeac-c5ef-46fa-9e99-408719296ed3}";

        perfil.FNC_FUNCIONALIDADES.Add(func1);
        user.PRF_PERFIS.Add(perfil);

        var funcs = new List<IFunctionality>();
        funcs.Add(new FunctionalityAttribute(1,"Associar Registros", "{0653aeac-c5ef-46fa-9e99-408719296ed3}", "SGIGT", true,"admin,editor"));

        var uid = new UIDClass() { Value = "{0653aeac-c5ef-46fa-9e99-408719296ed3}" };
        var arg = Arg<object>.Ref(Is.Anything(), 0).Dummy;

        // act
        controlBuilder.Build(user, funcs);

        // assert
        barMock.AssertWasCalled(x => x.Add(uid, ref arg), y => y.IgnoreArguments());
    }

Here is the error I'm finding:

Rhino.Mocks.Exceptions.ExpectationViolationException : ICommandBar.Add(ESRI.ArcGIS.esriSystem.UIDClass, 0); Expected #0, Actual #1.

I've already tried a number of ways, but it simply does not work. The build method implementation is below. This just receives a list of functions to build and an user, which has associated functions. If he has a function, it should be build, using the add method.

    public void Build(Usuario u, List<IFunctionality> funcsToBuild)
    {
        if (u == null)
            throw new ArgumentNullException("u");

        if (funcsToBuild == null)
            throw new ArgumentNullException("funcsToBuild");

        if (u.PRF_PERFIS == null || u.PRF_PERFIS.Count <= 0)
            return;

        var functionList = (from funcs in funcsToBuild
                            select funcs.FunctionDescription).ToList();

        var userFuncs = (from profile in u.PRF_PERFIS
                         from functionality in profile.FNC_FUNCIONALIDADES
                         where functionality.FNC_FL_ATIVO.HasValue && functionality.FNC_FL_ATIVO.Value && functionList.Contains(functionality.FNC_DE_FUNCIONALIDADE)
                         select new FunctionalityAttribute((int)functionality.FNC_NU_ID.Value,functionality.FNC_NO_CHAVE, functionality.FNC_DE_FUNCIONALIDADE, "SGIGT", true, "")).Cast<IFunctionality>().OrderBy(x => x.FunctionId).ToList();

        for (int index = 0; index < userFuncs.Count; index++)
        {
            IFunctionality t = userFuncs[index];
            object i = index;
            var functionality = t;
            var uid = new UIDClass() {Value = functionality.FunctionDescription};
            var ci = _commandBar.Add(uid, ref i);
        }
    }

Any chance someone can help me?

回答1:

You can use Arg<> to match on expectations for Out/Ref parameters. If you don't care about the actual value, you could use something like the following:

barMock.AssertWasCalled(x => 
              x.Add(Arg<UIDClass>.IsAnything(), 
              ref Arg<object>.Ref(Is.Anything(), null).Dummy);

If you do care about matching input/output values, then you could use this instead:

barMock.AssertWasCalled(x => 
              x.Add(Arg<UIDClass>.Is.Equal(uid), 
              ref Arg<object>.Ref(Is.Same(input), output).Dummy);

I think your test is most likely failing because uid is not the same instance as the one passed into ICommandBar, rather than a specific issue with the Ref parameter. You can solve this by using Equals as shown above, rather than using reference equality.

If all else fails and UIDClass doesn't implement equals correctly, you can always use a matching predicate:

barMock.AssertWasCalled(x => 
              x.Add(Arg<UIDClass>.Is.Matching(
                    delegate(UIDClass u) { return u.Value == uid.Value; }
              ), 
              ref Arg<object>.Ref(Is.Same(input), output).Dummy);


回答2:

    [Fact]
    public void Test_Ref()
    {
        var barMock = MockRepository.GenerateMock<IDoSomething>();

        Composer composer = new Composer(barMock);

        int number = 12;

        composer.addStuff(0, number);

        barMock.AssertWasCalled(x => x.Add(0, ref number), 
            y=>y.IgnoreArguments().Repeat.Times(1));
    }

    public class Composer
    {
        private IDoSomething dosomething;

        public Composer(IDoSomething doSomething)
        {
            this.dosomething = dosomething;
        }

        public void addStuff(int id, int value)
        {
            dosomething.Add(id, ref value);
        }

    }

    public interface IDoSomething
    {
        void Add(int id, ref int value);
    }

Well I think the issue may have to do with MockRepository.GenerateStrictMock() versus MockRepository.GenerateMock(); And then adding the Repeat.Times(1) on the IMethodoptions. You should be able to ensure that Add gets called based on the number of items in the list of funcs with this code appropriately.