Use FakeItEasy's A.CallTo() on another method in same object
Asked Answered
M

2

8

Using FakeItEasy, how do I check to see if my object's method calls another method on this same object?

The Test:

[TestMethod]
public void EatBanana_CallsWillEat()
{
  var banana = new Banana();
  var myMonkey = new Monkey();

  myMonkey.EatBanana(banana);

  //this throws an ArgumentException, because myMonkey is a real instance, not a fake
  A.CallTo(() => myMonkey.WillEat(banana)
    .MustHaveHappened();
}

The Class:

public class MyMonkey {
  private readonly IMonkeyRepo _monkeyRepo;

  public MyMonkey(IMonkeyRepo monkeyRepo) {
    _monkeyRepo = monkeyRepo;
  }

  public void EatBanana(Banana banana) {
    //make sure the monkey will eat the banana
    if (!this.WillEat(banana)) {
      return;
    }

    //do things here
  }

  public bool WillEat(Banana banana) {
    return !banana.IsRotten;
  }
}

I'm open to suggestions. If I'm going about this all wrong, please let me know.

Marathon answered 29/7, 2013 at 16:54 Comment(3)
This isn't really what FIE is for, as I understand it. FIE provides fake objects so your production code can be more easily poked and prodded. As you point out, you don't have a fake. In my experience, this kind of testing is usually not a good idea. Your MyMonkey class should be a fairly self-contained unit, and you'd be better off testing its overall behaviour when ordered to eat a banana, rather than worrying whether it called its own methods. For example, would you be able to tell whether the banana was eaten based on clues from what happens in "// do things here"?Maronite
@BlairConrad in my real scenario, WillEat is more complex, and has its own tests, and this is just one of the tests EatBanana has. In this test, if I let EatBanana call the real WillEat, won't I be testing two features in one test? Then, if WillEat changed, it could break that test, which is bad news, right?Marathon
I see your point. It's tricky. If WillEat lived on another object, we'd all be urging the faking of that object and injecting it into MyMonkey (sounds painful). All I can really say is that I think the best default position would be to try to test MyMonkey from the outside, relying on a client's observable results. But, you've put thought into it, and have found a solution that will save you pain and reduces the number of tests as well as the number that will break for a given. You know the code best, so if it works for you… I just wanted you to be aware of the alternative.Maronite
M
2

It's possible to do this. If the WillEat method were virtual - otherwise FakeItEasy won't be able to fake it out.

With that change, you could do this:

[TestMethod]
public void EatBanana_CallsWillEat()
{
    var fakeMonkey = A.Fake<MyMonkey>();

    fakeMonkey.EatBanana(new Banana());

    A.CallTo(()=>fakeMonkey.WillEat(A<Banana>._)).MustHaveHappened();
}

I'm still not convinced it's a good idea (as I ranted in the comments) - I think you'd be better off relying on other observable behaviour, but I'm not familiar with your system. If you think this is the best way to go, the example code should work for you.

Maronite answered 29/7, 2013 at 20:13 Comment(0)
T
3

Why are you mocking tested object? What exactly are you trying to test? The verification that call to WillEat happened is of little value. What information does it server to consumer? After all, consumer doesn't care how method is implemented. Consumer cares what are the results.

What happens when monkey eats banana that is not rotten? Your test should answer this question:

[TestMethod]
public void EatBanana_CAUSES_WHAT_WhenBananaIsNotRotten()
{
    var repo = A.Fake<IMonkeyRepo>();
    var monkey = new Monkey(repo);
    var freshBanana = new Banana { IsRotten = false };

    monkey.EatBanana(freshBanana);

    // verifications here depend on what you expect from
    // monkey eating fresh banana
}

Note that you can make all sort of verifications to IMonkeyRepo, which is properly faked and injected here.

Tabret answered 29/7, 2013 at 21:24 Comment(2)
Good point. If I don't mock out WillEat, and I allow EatBanana to call the real WillEat, am I testing two features in one test though? In this example, WillEat is trivial, but in my actual situation, it's more complex, and returns a derived object based on it's arguments, so I figured mocking it and verifying it was called would be the right way to go. I currently have separate tests for WillEat, and a couple other tests for EatBanana, including verifying that the repo was called. Maybe I'm getting too granular?Marathon
@JoshNoe: or maybe you're not getting granular enough. If WillEat is that complex, maybe it should live in its own class? Maybe it makes more sense to extract this feature to food-evaluator-sort-of-thing and inject it to monkey? Tests should be simple - if they're not it usually is a good indicator that design could be revisited. On a side note, why would Monkey depend on IMonkeyRepository?Tabret
M
2

It's possible to do this. If the WillEat method were virtual - otherwise FakeItEasy won't be able to fake it out.

With that change, you could do this:

[TestMethod]
public void EatBanana_CallsWillEat()
{
    var fakeMonkey = A.Fake<MyMonkey>();

    fakeMonkey.EatBanana(new Banana());

    A.CallTo(()=>fakeMonkey.WillEat(A<Banana>._)).MustHaveHappened();
}

I'm still not convinced it's a good idea (as I ranted in the comments) - I think you'd be better off relying on other observable behaviour, but I'm not familiar with your system. If you think this is the best way to go, the example code should work for you.

Maronite answered 29/7, 2013 at 20:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.