HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Decoupling tests from implementation and increase their resilience

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
testsdecouplingresilienceincreaseandimplementationfromtheir

Problem

I have read on numerous occasions that tests should not be coupled to implementation details - this makes perfect sense.

Now I am having some trouble decoupling my tests from implementation details and am kindly asking for some help.

The relevant portion of the repository that I am testing looks like this:

public class PostsRepository : IPostsRepository
{
    private readonly DatabaseContext context;

    public PostsRepository(DatabaseContext context)
    {
        this.context = context;
    }

    public Post Find(string slug)
    {
        return context
            .Posts
            .SingleOrDefault(post => post.Slug == slug);
    }

    public bool Delete(string slug)
    {
        var post = Find(slug);

        if (post == null) return false;

        context.Posts.Remove(post);
        return true;
    }
}


The context is mocked using the technique described here and then manually injected into the repository.

The corresponding tests that I am concerned about look like this:

[Test]
public void Delete_ExistentPost_DeletesPost()
{
    databaseSet.SetupSeedData(new List
    {
        PostsMother.CreatePost(withSlug: "abc")
    });

    repository.Delete("abc");

    databaseSet.Verify(d => d.Remove(It.IsAny()));
}

[Test]
public void Delete_NonExistentPost_DoesNotThrow()
{
    repository.Delete("abc");
}

[Test]
public void Delete_ExistentPost_ReturnsTrue()
{
    databaseSet.SetupSeedData(new List
    {
        PostsMother.CreatePost(withSlug: "abc")
    });

    var actual = repository.Delete("abc");
    Assert.True(actual);
}

[Test]
public void Delete_NonExistentPost_ReturnsFalse()
{
    var actual = repository.Delete("abc");
    Assert.False(actual);
}


These tests seem to work well enough but I am concerned about their brittleness. If you look at the implementation of the Delete method you will see that it uses the Remove method but what if that changed? The test would break.

Furthermore (and more importantly), the test is hig

Solution

Without trying to sound tautological, the tests in your original question are coupled to your implementation because that's exactly what they are testing.

You're testing that -

  • Remove is called on a database abstraction



  • The method doesn't throw an exception for an invalid slug



  • The method returns true when it finds something to delete



  • The method returns false when it does not find something to delete



These are all quite low level concepts, are any of them important?

As a developer that's new to a code base, I look to the tests to tell me what's important and needs to be preserved. Most of the time, this boils down to behaviour that the user can observe and interact with.

If you re-focus your tests so that they ensure -

  • When there are Posts in the repository, a user can view them



  • When a Post is deleted, it is no longer listed on an index



  • When a Post is deleted, it can no longer be viewed



Then you will not be coupling them to such details as "my repositories return false when they can't find something to delete".

Context

StackExchange Code Review Q#61485, answer score: 4

Revisions (0)

No revisions yet.