Search code examples
c#.netrhino-mocks

How do I return a different value from a stub based on a lambda argument


I have the following sample test code

    public Stage Test(Stage Stage)
    {
        var StartStage = StageRepository.Single(x => x.Order == 1);
        var EndStage = StageRepository.Single(x => x.Order == 5);
        var ErrorStage = StageRepository.Single(x => x.Name == "Error");

        if (Stage == StartStage)
        {
            return EndStage;
        }
        else
        {
            return ErrorStage;
        }
    }

And I am trying to test it using the following unit test

    [TestMethod]
    public void XXXTest()
    {
        //// Arrange
        var AutoMocker = new RhinoAutoMocker<StageService>(MockMode.AAA);

        MockRepository mockRepository = new MockRepository();

        var MockStageRepository = AutoMocker.Get<IRepository<Stage>>();

        Stage StartStage = mockRepository.Stub<Stage>();
        StartStage.Order = 1;

        Stage EndStage = mockRepository.Stub<Stage>();
        EndStage.Order = 5;

        Stage ErrorStage = mockRepository.Stub<Stage>();
        ErrorStage.Name = "Error";

        System.Linq.Expressions.Expression<Func<Entities.Stage, bool>> StartParam = x => x.Order == 1;
        MockStageRepository
            .Stub(x => x.Single(Arg<System.Linq.Expressions.Expression<Func<Entities.Stage, bool>>>.Is.Equal(StartParam)))
            .Return(StartStage);

        System.Linq.Expressions.Expression<Func<Entities.Stage, bool>> EndParam = x => x.Order == 1;
        MockStageRepository
            .Stub(x => x.Single(Arg<System.Linq.Expressions.Expression<Func<Entities.Stage, bool>>>.Is.Equal(EndParam)))
            .Return(EndStage);

        System.Linq.Expressions.Expression<Func<Entities.Stage, bool>> ErrorParam = x => x.Order == 1;
        MockStageRepository
            .Stub(x => x.Single(Arg<System.Linq.Expressions.Expression<Func<Entities.Stage, bool>>>.Is.Equal(ErrorParam)))
            .Return(ErrorStage);

        StageService StageService = AutoMocker.ClassUnderTest;


        //Act
        var ReturnStage = StageService.Test(StartStage);


        //Assert
        Assert.AreEqual(ReturnStage, EndStage);
    }

However this is not working as it is not returning anything when I call StageRepository.Single(). If I change the stub code to ignore the argument then it does return something but it will be the same object returned for each call to Single() which I don't want.

Is it possible to configure RhinoMocks in such a way as to return different objects from a stub depending on the lambda that is passed into it?


Solution

  • I think the root of your problem is that equality on the Expression<Func<T,U>> type is performed by reference rather than value. That means your telling Rhino Mocks to look for instances of expressions created in the test rather than the ones created in the code your testing.

    Two possible approaches come to mind:

    • One would be to provide a way to pass the lambda expressions in to the Stage class from the test, so that when the argument checks happen they are working against the same instances.

      Maybe something like:

      internal void SetStartStage(Expression<Func<Entities.Stage,bool>> predicate)
      {
          ...
      }
      

      The inverse of this would also work, i.e. provide the Expression objects as fields/properties that can be accessed by you're test, and then use those when setting up your mock:

      internal Expression<Func<Entities.State,bool>> StartStagePredicate
      {
          get{ return x => x.Order == 1; }
      }
      
    • Another option would be to use the Matches method on Args to see if the argument checks the Stage object state correctly. This would require creating some Stage objects in your test that would match the criteria:

      var startStageTester = new Stage { Order = 1 };
      MockStageRepository
              .Stub(x => x.Single(Arg<System.Linq.Expressions.Expression<Func<Entities.Stage, bool>>>.Matches(y => y.Compile()(startStageTester)))
              .Return(StartStage);
      

      The call to Compile() is a little jarring, but since you're dealing with an Expression and not a straight-up lambda, you've got to compile it in order to evaluate it.

      Now, if a Stage is something that is hard to create, then you may need to create a Mock/Stub of one (or use you're StartStage) and have it return 1 from the Order property (in the case of the StartStage anyway).

    There are probably some others I'm not thinking of at the moment as well.