Mocking a method within a method with FakeItEasy
Asked Answered
U

2

5

How can I mock/facke the result from a function that is called in another function? Usually Test2 would be a DataAccess method that I dont like to fetch real data. What I like my unittest to test is the business logic.

This Is what I have now but its not working at all. Sum is always asserted as 5!

public int Test1()
{
    var value = this.Test2(); //Unittest should substitute with 5
    var businesslogic = value + 10; //The business logic

    return businesslogic;
}

public int Test2()
{
    return 10; //I try to mock this value away in the test. Don´t go here!
}

Then I have a Unittest that I would like to run on my "business logic".

[TestMethod()]
public void TestToTest()
{
//Arrange
var instance = A.Fake<IClassWithMethods>();

      //Make calling Test2 return 5 and not 10.
A.CallTo(() => instance.Test2()).Returns(5);

      //Call the method 
var sum = instance.Test1();

//Assert if the business logic in the method works.
Assert.AreEqual(15, sum);
}
Upsweep answered 12/2, 2014 at 16:34 Comment(0)
C
5

First, let me say that I think there are fantastic points in Tseng's answer, especially about how

  1. Faking an interface means the "inner methods" can never be called, and
  2. You should be faking dependencies, not internals. If you have the option to make this change, do it, and stop reading my answer right now.

If you're still reading, I'm confused about two things:

  1. should Test1 return businessLogic? As it's written (once the compilation errors are fixed), I would expect Test1 to return 5 (not 15) when Test2 returns 5.
  2. In TestToTest, when you set the Returns(5) on Test2, and then call Test1, since you faked an interface, I would expect sum to be 0, the default value for an int. I'm not sure how you'd get 5. In fact, I've replicated this behaviour in this test:

[TestMethod]
public void TestToTestInterface()
{
    //Arrange

    var instance = A.Fake<IClassWithMethods>();

    //Make calling Test2 return 5 and not 10.
    A.CallTo(() => instance.Test2()).Returns(5);

    //Call the method 
    var sum = instance.Test1();

    //Assert if the business logic in the method works.
    Assert.AreEqual(0, sum); // because Test1 wasn't faked
}

While I don't care for the approach myself, if you really want to have your replaceable code in ClassWithMethods, and then to test the Test1 method, there's a way:

  1. We need to make Test1 and Test2 virtual, or they won't be fakeable.
  2. Fake out ClassWithMethods, not IClasssWithMethods.
  3. Tell the fake that when Test1 is invoked, it should call the original code (which will in turn call the fake Test2).

I've put these changes all together, and this test passes for me:

public class ClassWithMethods : IClassWithMethods
{
    public virtual int Test1()
    {
        var value = this.Test2(); //Unittest should substitute with 5
        var businesslogic = value + 10; //The business logic

        return businesslogic;
    }

    public virtual int Test2()
    {
        return 10; //I try to mock this value away in the test. Don´t go here!
    }
}

[TestMethod]
public void TestToTestClass()
{
    //Arrange

    var instance = A.Fake<ClassWithMethods>();

    //Make calling Test2 return 5 and not 10.
    A.CallTo(() => instance.Test2()).Returns(5);

    // Make sure that Test1 on our fake calls the original ClassWithMethods.Test1
    A.CallTo(() => instance.Test1()).CallsBaseMethod();

    //Call the method 
    var sum = instance.Test1();

    //Assert if the business logic in the method works.
    Assert.AreEqual(15, sum);
}
Cosine answered 12/2, 2014 at 20:34 Comment(3)
Great answer. I fixed my code in the question to answer your first question. But regardless from this exact example what im realy trying to do is to be able to test logic in a method without calling the other methods that are called there in. Like if i had some insert into database in Test2 i would not like to call that method but i would like to be able to test the business logic of the "value + 10" portion of the method. I agree that i dont like the virtual solution, changing the methods seem to drastic.Upsweep
If you don't want to change any methods, then I don't see how you're going to be able to accomplish the kind of testing you want. Both my and Tseng's suggestions require some modification to the code, with Tseng's being more dramatic (and superior, I think). I think you would need to use a mocking framework such as TypeMock Isolator or Microsoft Fakes if you were to avoid modifying your code.Cosine
Thank you very much. Microsoft Fakes was just what I was searching for. Here is a great link to start using it codeproject.com/Articles/582812/…Upsweep
C
6

You can't do that as far as I know.

Your instance is not an instance of the real class, just a mockup on it's interface, hence a call to instance.Test1() won't call the code you described above. You can however UnitTest Test2 Method on it's own.

What you can do however is, make 2 Unit Tests.

In the first test (Testing Method Test2), you instantiate your class with the necessary dependencies (or if there are no dependencies with certain values/parameters).

Then do a second test, with same input parameters, and Testing Test() Method.

Mockups are only used for dependencies where you have to mock up on an interface (which is instantiated outside of the class you test). i.e. if you have ClassA and ClassB and ClassA depends on IClassB interface. Then you can mock B to test A.

Cave answered 12/2, 2014 at 16:46 Comment(0)
C
5

First, let me say that I think there are fantastic points in Tseng's answer, especially about how

  1. Faking an interface means the "inner methods" can never be called, and
  2. You should be faking dependencies, not internals. If you have the option to make this change, do it, and stop reading my answer right now.

If you're still reading, I'm confused about two things:

  1. should Test1 return businessLogic? As it's written (once the compilation errors are fixed), I would expect Test1 to return 5 (not 15) when Test2 returns 5.
  2. In TestToTest, when you set the Returns(5) on Test2, and then call Test1, since you faked an interface, I would expect sum to be 0, the default value for an int. I'm not sure how you'd get 5. In fact, I've replicated this behaviour in this test:

[TestMethod]
public void TestToTestInterface()
{
    //Arrange

    var instance = A.Fake<IClassWithMethods>();

    //Make calling Test2 return 5 and not 10.
    A.CallTo(() => instance.Test2()).Returns(5);

    //Call the method 
    var sum = instance.Test1();

    //Assert if the business logic in the method works.
    Assert.AreEqual(0, sum); // because Test1 wasn't faked
}

While I don't care for the approach myself, if you really want to have your replaceable code in ClassWithMethods, and then to test the Test1 method, there's a way:

  1. We need to make Test1 and Test2 virtual, or they won't be fakeable.
  2. Fake out ClassWithMethods, not IClasssWithMethods.
  3. Tell the fake that when Test1 is invoked, it should call the original code (which will in turn call the fake Test2).

I've put these changes all together, and this test passes for me:

public class ClassWithMethods : IClassWithMethods
{
    public virtual int Test1()
    {
        var value = this.Test2(); //Unittest should substitute with 5
        var businesslogic = value + 10; //The business logic

        return businesslogic;
    }

    public virtual int Test2()
    {
        return 10; //I try to mock this value away in the test. Don´t go here!
    }
}

[TestMethod]
public void TestToTestClass()
{
    //Arrange

    var instance = A.Fake<ClassWithMethods>();

    //Make calling Test2 return 5 and not 10.
    A.CallTo(() => instance.Test2()).Returns(5);

    // Make sure that Test1 on our fake calls the original ClassWithMethods.Test1
    A.CallTo(() => instance.Test1()).CallsBaseMethod();

    //Call the method 
    var sum = instance.Test1();

    //Assert if the business logic in the method works.
    Assert.AreEqual(15, sum);
}
Cosine answered 12/2, 2014 at 20:34 Comment(3)
Great answer. I fixed my code in the question to answer your first question. But regardless from this exact example what im realy trying to do is to be able to test logic in a method without calling the other methods that are called there in. Like if i had some insert into database in Test2 i would not like to call that method but i would like to be able to test the business logic of the "value + 10" portion of the method. I agree that i dont like the virtual solution, changing the methods seem to drastic.Upsweep
If you don't want to change any methods, then I don't see how you're going to be able to accomplish the kind of testing you want. Both my and Tseng's suggestions require some modification to the code, with Tseng's being more dramatic (and superior, I think). I think you would need to use a mocking framework such as TypeMock Isolator or Microsoft Fakes if you were to avoid modifying your code.Cosine
Thank you very much. Microsoft Fakes was just what I was searching for. Here is a great link to start using it codeproject.com/Articles/582812/…Upsweep

© 2022 - 2024 — McMap. All rights reserved.