3/06/2014

FakeItEasy: be careful when wrapping an existing object


Isolation frameworks are very popular today — they are an important part of your automated tests. They allow to easily isolate dependencies you don’t control, such as file systems or network connections, using fake but controllable objects generated on-the-fly via Reflection. On my last project I used FakeItEasy — a nice framework with clean API. I like it very much (RIP, Moq)). This post was inspired by a real-life situation.

Fake for an already created object

Normally you create fakes for interfaces or abstract objects. But suppose you want to partially fake a concrete object that has virtual methods:

Dog dog = A.Fake<Dog>();

Where the class Dog is implemented like this:

public class Dog
{
    public virtual string Bark()
    {
        return "Bark!";
    }

    public virtual string BarkBark()
    {
        return Bark() + Bark();
    }
}

For that fake dog both methods will return an empty string. Here is sample test to confirm this:

public void Bark_DespiteExistingImplementation_ReturnsEmptyString()
{
    Dog dog = A.Fake<Dog>();

    string result = dog.Bark();

    Assert.AreEqual("", result);
}

Hmm... As you can see the method Bark() already has the logic that returns string "Bark!", but it is thrown away for the fake object, as if it were based on the completely abstract IDog interface. The same behavior applies to NSubstitute framework. Moq returns null instead of an empty string.
If you want the logic implemented in virtual methods to be preserved, you have to create a fake that wraps an existing object:

Dog dog = A.Fake<Dog>(x => x.Wrapping(realDog));

Lets write a test to confirm:

[Test]
public void Bark_ForWrappedObject_ReturnsImplementationFromThatObject()
{
    Dog realDog = new Dog();
    Dog dog = A.Fake<Dog>(x => x.Wrapping(realDog));

    string result = dog.Bark();

    Assert.AreEqual("Bark!", result);
}

Great. Now, suppose you want to override the method Bark() on faked object to return the string "Quack!". Simple to do:

A.CallTo(() => dog.Bark()).Returns("Quack!");

And now the question: what do you expect to be returned from the method BarkBark()? It calls the virtual method Bark() that you overrode with A.CallTo() construct. But...

[Test]
public void BarkBark_ForOverriddenBark_UsesOverridenImplementation()
{
    Dog realDog = new Dog();
    Dog dog = A.Fake<Dog>(x => x.Wrapping(realDog));
    A.CallTo(() => dog.Bark()).Returns("Quack!");

    string result = dog.BarkBark();

    // Not what you expected    
    Assert.AreEqual("Quack!Quack!", result); // Oops, "Bark!Bark!"
}

For those who know how virtual methods work this looks very counter-intuitive. So be aware of this behavior!
But if you do need to override the method Bark() in the traditional "virtual manner", what should you do? Unfortunately, FakeItEasy cannot help you in this situation. You have to manually code the FakeDog class and override the method Bark():

public class FakeDog : Dog
{
    public override string Bark()
    {
        return "Quack!";
    }
}


[Test]
public void BarkBark_ForManualFake_UsesOverridenImplementation()
{
    Dog dog = new FakeDog();

    string result = dog.BarkBark();

    Assert.AreEqual("Quack!Quack!", result); // SUCCESS!
}

By the way, with NSubstitute framework you'll experience the very same issue. Surprisingly, Moq has a simple solution to the problem — just set the CallBase property to true.


Conclusion

Try to avoid situations when your tests use  the same object as SUT (System Under Test) and a fake. Like in our example: we tested the class Dog which was our SUT and we also faked a part of it (method Bark()). As you can see, this can lead to surprising results when used in combination with some of the popular isolation frameworks.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.