Exploring alternatives to tautological tests

What is a Tautological Test? Get some background here.

Not every tautological test uses mocks.

“Have you got 10 mins?”

“Sure”

“I’ve been thinking about tautological tests. I’ve got a mapper that I’m TDDing. The test I want to write seems tautological. Any thoughts?”

public class Mapper
{
    public EntityItem Map(DomainItem domain)
    {
        return new EntityItem() { Id = domain.DomainId, EntityCode = domain.code };
    }
}
	
[Test]
public void When_Map_is_called_Then_all_fields_mapped_successfully()
{
    var originalItem = new DomainItem() { DomainId = 1, Code = "ABC" };

    var sut = new Mapper();
    var mappedItem = sut.Map(originalItem);

    Assert.That(mappedItem.Id, Is.EqualTo(originalItem.DomainId));
    Assert.That(mappedItem.EntityCode, Is.EqualTo(originalItem.Code));
}

“It does feel that way. The assertion code is doing the same work as the mapper.  It repeats the mapper’s work. It knows which fields map to which.… but…”

“…but how can you avoid it?!”

“Right.”

We want to write code in small maintainable chunks, but the smaller they get, the harder it is to express the tests in a non-tautological way.

Is this a sign that we should make the units bigger?

So one thought I had about this problem was, why not test the mapper in context? We could test the next class that uses it. It’s not great though. While that is a perfectly viable way of testing the next class up, it falls a little short because we’d like to be able to test edge cases on this mapper in case it ever got more complicated – perhaps with value conversions and so on.

“Of course I’ll still need another test.” Pete fiddled with the code. “Because otherwise I can implement it to return constants and it’ll pass.”

“But we’d need that test anyway.”

“We should focus on the assertion.  It shouldn’t know how the mapping was done.”

We poked the code a bit and thought.

“I saw this nice refactor on xunitpatterns.com – it’s one I already use but hadn’t known the name of before.”

We visited the page.

“So we can replace the assertions like this and compare to an Expected Object.”

[Test]
public void When_Map_is_called_Then_all_fields_mapped_successfully()
{
    var originalItem = new DomainItem() {DomainId = 1, Code = "ABC"};

    var sut = new Mapper();
    var mappedItem = sut.Map(originalItem);
    
    var expectedItem = new EntityItem() { Id = 1, EntityCode = "ABC" };
    AssertItemsAreEqual(expectedItem, mappedItem);
}

private void AssertItemsAreEqual(EntityItem expectedItem, EntityItem mappedItem)
{
    Assert.That(mappedItem.Id, Is.EqualTo(expectedItem.Id));
    Assert.That(mappedItem.EntityCode, Is.EqualTo(expectedItem.EntityCode));
}

Because we are supplying an input and an expected output, and making no assumptions about how we get there, this test is now definitely not tautological. But the difference was subtle.

“I like the fact that we’re comparing an EntityItem to an EntityItem, instead of properties on two different objects.”

“I hadn’t thought of that before.  I’ve been doing this because the new assertion method is reusable – and as long as we name it well, it gives us more information about what we are actually asserting.  It’s really important to me that a test is as easy to understand as possible.”

“Hey now we have just have one assertion in the test – as far as the test is concerned, the equality check is one unit of work.  That’s good!”

Imagine that we had added more logic to the mapper:

[Test]
public void When_Map_is_called_Then_all_fields_mapped_successfully()
{
    var originalItem = new DomainItem() {DomainId = 1, Code = "ABC" };

    var sut = new Mapper();
    var mappedItem = sut.Map(originalItem);

    Assert.That(mappedItem.Id, Is.EqualTo(originalItem.DomainId));
    Assert.That(mappedItem.EntityCode, Is.EqualTo(originalItem.Code));
    Assert.That(mappedItem.IdCode, 
        Is.EqualTo(originalItem.DomainId + "-" + originalItem.Code));
}

Now it’s clear that this test – which has exactly the same structure as the original – knows too much.  And we’re asking the reader to do a lot of thinking in order to check the test makes sense.

The expected object pattern helps significantly, making us write assertions from a point of view that is not tautological – no calculations involved; no assumptions about which field is which.

[Test]
public void When_Map_is_called_Then_all_fields_mapped_successfully()
{
    var originalItem = new DomainItem() {DomainId = 1, Code = "ABC"};

    var sut = new Mapper();
    var mappedItem = sut.Map(originalItem);
	
    var expectedItem = new EntityItem()
         { Id = 1, EntityCode = "ABC", IdCode = "1-ABC" };
    AssertItemsAreEqual(expectedItem, mappedItem);
}

Because the mapper code could be more complicated in the future, it’s nice that we’ve ended up with a test pattern which cleanly serves our needs now, and is future proof.

Leave a Reply

Your email address will not be published. Required fields are marked *