Question

Imagine that I have the following class:

class ToTest : IToTest
{
    public string MethodA()
    {
        return "Test";
    }

    public int MethodB()
    {
        returns a random number;
    }

    public string MethodC()
    {
        return this.MethodA + " " + this.MethodB.ToString();
    }
}

I'm testing now MethodC so I understand that I should mock MethodA and MethodB of my current instance so I only test MethodC validity right?

I'm using Rhino and I did the following:

ToTest testedObject = new ToTest();

testedObject.Expect(t => t.MethodA).Returns("AString");
testedObject.Expect(t => t.MethodB).Returns(1324");

Assert.AreEqual("AString 1324", testedObject.MethodC());

But I get an error correctly saying that the testedObject is not a Mock.

Is the approach right? How should I proceeed?

Was it helpful?

Solution

No, please do not write test cases like this. Unit tests are not about "testing just one thing", they are all about ensuring that each test is a unit, ie it has no influence on any other test.

All your tests should be interested in is the public API of your class. Do not test internals or private methods, they are part of the inner workings of your class, and do not try to mock parts of your class in order to test other parts. Your test of MethodC must indirectly test MethodA and MethodB too, otherwise your test is meaningless.

For anyone interested in an excellent talk on how to write good unit tests, I'd recommend setting aside an hour and watching the 'Ian Cooper: TDD, where did it all go wrong' video from NDC 2013.

OTHER TIPS

Based off the example that you are having us imagine, the simple answer to your question is your approach is not correct. By having the trouble you are experiencing, the code is trying to tell you that you have some mixed concerns in the code that are coupled too tightly together, and that instead of having cohesive code, you are having adhesive code. (Glenn Vanderburg has a nice blog entry on the topic of cohesion found at http://www.vanderburg.org/Blog/Software/Development/cohesion.rdoc)

If you are feeling the need to mock out MethodA and MethodB to test MethodC this is a signal that one of a few things, possibly all of them even, are missing in the code.

The first thing the difficulty is telling you is that MethodC may likely be in the wrong location in the code. MethodC may very well belong to another object which takes and object with MethodA and MethodB as a dependency, either through constructor injection, or parameter injection.

The second thing that stands out, is that you are having trouble testing MethodC because MethodB has a dependency on what would lead me to think of being a global/singleton behavior. You have defined MethodB as returns a random number, instead of taking a dependency on a object that fills a role of NumberGenerator. By explicitly calling out the dependency on this role (Interface), it would allow you to pass in different implementations of the role depending on the usage. Three obvious implementations that could be used and easily swapped out depending on if it is production code or test code would be:

  • a RandomNumberGenerator: returns a random number every time it is called,
  • a SequentialNumberGenerator: returns the next number in a sequence (of which multiple types of sequences could be generated), such as increasing by a set number every time, or some kind of a mathamatical sequence, e.g. Fibbonacci or Collatz
  • a ContsistantNumberGenerator: returns the same number every single time it is called, which is very useful in testing.

The third thing that your example may be telling you is a refinement of the previous issue, calling out that you have a dependency on not only mutable state which you can't control, but non-deterministic mutable state, which you would have no means of coercing into a given state, even by sending a number of other messages to objects to try to get into that state. I know of no messages that you can send that will setup the environment to return a desired random number upon the next call to get it; even if there is such a way, the amount of setup that would be required would obscure what you are actually trying to test.

I hope that one of these suggestions help you address your issue.

That won't work for two reasons (Assuming you're using Rhino.Mocks):

  • You cannot mock a method that is not virtual.
  • You can only test whole instances (or static members). That means, when you want to individually test methods inside the class without the others, you need some workaround, i.e. by providing a factory or by moving MethodC into a different class than MethodA and MethodB.

A working code for your example will be something like this:

public class ToTest
{
    private Random random = new Random();

    public virtual string MethodA()
    {
        return "Test";
    }

    public virtual int MethodB()
    {
        return random.Next();
    }

    public virtual string MethodC()
    {
        return MethodA() + " " + MethodB();
    }
}

[TestFixture]
public class tests
{
    [Test]
    public void Test_MethodC()
    {
        var mocks = new MockRepository();
        ToTest testedObject = mocks.CreateMock<ToTest>();

        testedObject.Expect(t => t.MethodA()).Return("AString");
        testedObject.Expect(t => t.MethodB()).Return(1324);

        Assert.AreEqual("AString 1324", testedObject.MethodC());
    }
}

BUT!

This will not pass the test. And the cause is that Rhino.Mocks, like most other mocking frameworks, excluding MS Moles/Shims, TypeMock and others using profiler API, cannot enforce the type to see the mocked methods from inside. This is because mocking frameworks create a proxy type around the original one and inject the mocking logic into the proxy, not into the original type itself.

So, as suggested, you should extract MethodA and MethodB into a separate dependencies, in case you have something to test inside MethodC.

Another, perfectly viable way is to derive from the ToTest class, override the MethodA and MethodB, and test the instance of the derived class.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top