Tohid
Tohid

Reputation: 6679

What is the right way to unit-test a function that calls another public function on the same object?

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).

Upvotes: 1

Views: 914

Answers (3)

JamesR
JamesR

Reputation: 745

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.

Upvotes: 1

Glauco Cucchiar
Glauco Cucchiar

Reputation: 774

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)

Upvotes: 0

Mat&#237;as Fidemraizer
Mat&#237;as Fidemraizer

Reputation: 64933

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.

Upvotes: 7

Related Questions