1

While I am aware of this question/answers (Unit testing a method that calls another method), still not sure what is the best approach to unit-test a method that calls another public method on the same class?

I made a sample code (it can be seen here too: dotnetfiddle.net/I07RMg )

public class MyEntity
{
    public int ID { get; set;}
    public string Title { get; set;}
}

public interface IMyService
{
    MyEntity GetEntity(int entityId);
    IEnumerable<MyEntity> GetAllEntities();
}

public sealed class MyService : IMyService
{
    IRepository _repository;

    public MyService(IRepository repository)
    {
        _repository = repository;
    }

    public MyEntity GetEntity(int entityId)
    {
        var entities = GetAllEntities();
        var entity = entities.SingleOrDefault(e => e.ID == entityId);

        if (entity == null)
        {
            entity = new MyEntity { ID = -1, Title = "New Entity" };
        }

        return entity;
    }

    public IEnumerable<MyEntity> GetAllEntities()
    {
        var entities = _repository.Get();

        //Some rules and logics here, like:

        entities = entities.Where(e => !string.IsNullOrEmpty(e.Title));
        return entities; // To broke the test: return new List<MyEntity>();
    }
}

public interface IRepository : IDisposable
{
    IEnumerable<MyEntity> Get();
}

So the question is how to write a unit test that only tests the logic inside MyService.GetEntity(int)? (while GetEntity internally calls GetAllEntities() but I am not interested to test latter one).

Community
  • 1
  • 1
Tohid
  • 6,175
  • 7
  • 51
  • 80

3 Answers3

7

I really believe that unit testing isn't about mocking even methods of the same class.

When we talk about units we should refer to parts of your software. That is, you want to test GetEntity and the fact that it also calls GetAllEntities under the hoods is just an implementation detail.

What you really need is to be sure that, when you test your service, any injected dependency (repository, other services collaborating in the domain...) should be replaced with fakes to be sure that you're just testing your service, or you'll be implementing an integration test.

OP said in some comment...

I understand. The only caveat is if the logics inside the GetAllEntities() fails, it might also breaks Unit Tests for GetEntity(). Then it's a bit harder to pin point where the real bug lies.

As I said previously on this answer, the fact that GetEntity calls GetAllEntities is just an implementation detail. It's like if you would re-implement (argh, copy-paste programming...) the logic of GetAllEntities inside GetEntity. Who cares.

Actually, if GetEntity fails because GetAllEntities, the test for GetAllEntities itself will also fail. What test will you try to address first? I suspect that once you've realized that GetEntities fails because of GetAllEntities and also GetAllEntities test fails, you would go directly to fix GetAllEntities test, won't you?

In summary, the way you've described your concern, the bug would be on GetAllEntities and it's absolutely expectable that any other method relying on GetAllEntities could fail too.

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • I understand. The only caveat is if the logics inside the `GetAllEntities()` fails, it might also breaks Unit Tests for `GetEntity()`. Then it's a bit harder to pin point where the real bug lies. – Tohid Apr 01 '16 at 18:59
  • Matias Fidermaizer - Please take a look at JamesR's answer. I do like it better, but unfortunately TypeMock is the only mocking framework that supports it, as far as I find out. – Tohid Apr 07 '16 at 17:03
  • @Tohid This is up to you, my humble opinion is you shouldn't be that paranoic, and at the end of the day, you'll end up mocking too much and, just think what are you really testing? If some method calls other and the caller has, for example, 3 lines of code, that test will be practically useless. – Matías Fidemraizer Apr 07 '16 at 19:55
  • 1
    @Tohid If you feel that mocking at the whole level as suggested by the other answer works better in your scenario, there's no problem, StackOverflow has the right answer for you and others that can be also useful for future visitors – Matías Fidemraizer Apr 07 '16 at 19:56
1

You can test it by mocking function GetAllEntities(), using mocking framework (I use Typemock Isolator).

Here is a simple example:

[TestMethod, Isolated]
public void TestGetCreatesNewEntity()
{
    //Assert
    IRepository someRepository = new MyRepository();
    MyService service = new MyService(someRepository);

    List<MyEntity> entities = new List<MyEntity>();
    Isolate.WhenCalled(() => service.GetAllEntities()).WillReturn(entities.AsQueryable());

    //Act
    MyEntity result = service.GetEntity(1);

    //Assert
    Assert.AreEqual(-1, result.ID);
    Assert.AreEqual("New Entity", result.Title);
}

Hope it'll help you.

JamesR
  • 745
  • 4
  • 15
  • JamesR - hmmm. I use `Moq` [ https://github.com/moq/moq4 ]. I should see if this is possible with my mocking framework. – Tohid Apr 07 '16 at 08:35
  • JamesR - It seems that Moq doesn't have this feature but there's another way (`.CallBase()`), that can go around it: http://stackoverflow.com/a/2462672/538387 I will test it an let you know if it works. – Tohid Apr 07 '16 at 09:24
  • JamesR - Turns out that TypeMock is the only well-known framework that supports mocking concrete `sealed` classes. Moq cannot do that. Thank you anyway. +1 up. – Tohid Apr 07 '16 at 16:59
  • That's why I'm using it. You're welcome, hope u'll enjoy it too – JamesR Apr 10 '16 at 07:40
0

You can increase your design and testeability creating a virtual GetAllEntities methods, subclass your service with MyTesteableService:

public class MyTesteableService : MyService
{
    public override IEnumerable<MyEntity> GetAllEntities()
    {
        return something;
    }
}

Now you can test your new testeable service without use GetAllEntities logic. however, you will have to test the behavior of GetEntity and verify the call to GetAllEntities.

So, in another manner, you can think your service as an abstract class with virtual GetEntity and abstract GetAllEntities. I use RhinoMock and I can do this with PartialMock (http://ayende.com/wiki/Rhino+Mocks+Partial+Mocks.ashx?AspxAutoDetectCookieSupport=1)

Glauco Cucchiar
  • 764
  • 5
  • 19
  • You aware that `MyService` is a sealed class, correct? – Tohid Apr 04 '16 at 13:45
  • 1
    yes, my example is to explain design. If you really need to make your service sealed, then create a baseService with abstract GetAllEntities like my second concept: "in another manner, you can think your service as an abstract class..." – Glauco Cucchiar Apr 04 '16 at 13:50