patterncsharpMinor
Decoupling tests from implementation and increase their resilience
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:
The
The corresponding tests that I am concerned about look like this:
These tests seem to work well enough but I am concerned about their brittleness. If you look at the implementation of the
Furthermore (and more importantly), the test is hig
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 -
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 -
Then you will not be coupling them to such details as "my repositories return false when they can't find something to delete".
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
Postsin the repository, a user can view them
- When a
Postis deleted, it is no longer listed on an index
- When a
Postis 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.