Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I avoid multiple asserts in this unit test?

This is my first attempt to do unit tests, so please be patient with me.
I'm still trying to unit test a library that converts lists of POCOs to ADO.Recordsets.

Right now, I'm trying to write a test that creates a List<Poco>, converts it into a Recordset (using the method I want to test) and then checks if they contain the same information (like, if Poco.Foo == RS.Foo, and so on...).

This is the POCO:

public class TestPoco
{
    public string StringValue { get; set; }
    public int Int32Value { get; set; }
    public bool BoolValue { get; set; }
}

...and this is the test so far (I'm using xUnit.net):

[Fact]
public void TheTest()
{
    var input = new List<TestPoco>();
    input.Add(new TestPoco { BoolValue = true, Int32Value = 1, StringValue = "foo" });

    var actual = input.ToRecordset();

    Assert.Equal(actual.BoolValue, true);
    Assert.Equal(actual.Int32Value, 1);
    Assert.Equal(actual.StringValue, "foo");
}

What I don't like about this are the three asserts at the end, one per property of the POCO.
I've read lots of times that multiple asserts in one test are evil (and I understand the reasons why, and I agree).

The problem is, how can I get rid of them?

I have Roy Osherove's excellent book "The Art of Unit Testing" right in front of me, and he has an example which covers exactly this (for those who have the book: chapter 7.2.6, page 202/203):

In his example, the method under test returns an AnalyzedOutput object with several properties, and he wants to assert all the properties to check if each one contains the expected value.

The solution in this case:
Create another AnalyzedOutput instance, fill it with the expected values and assert if it's equal to the one returned by the method under test (and override Equals() to be able to do this).

But I think I can't do this in my case, because the method that I want to test returns an ADODB.Recordset.

And in order to create another Recordset with the expected values, I would first need to create it completely from scratch:

// this probably doesn't actually compile, the actual conversion method 
// doesn't exist yet and this is just to show the idea

var expected = new ADODB.RecordsetClass();
expected.Fields.Append("BoolValue", ADODB.DataTypeEnum.adBoolean);
expected.Fields.Append("Int32Value", ADODB.DataTypeEnum.adInteger);
expected.Fields.Append("StringValue", ADODB.DataTypeEnum.adVarWChar);

expected.AddNew();
expected.BoolValue = true;
expected.Int32Value = 1;
expected.StringValue = "foo";
expected.Update();

I don't like this either, because this is basically a duplication of some of the code in the actual conversion method (the method under test), which is another thing to avoid in tests.

So...what can I do now?
Is this level of duplication still acceptable in this special situation, or is there a better way how to test this?

like image 639
Christian Specht Avatar asked Jan 10 '12 00:01

Christian Specht


People also ask

What do you have to avoid in tests in unit testing?

Avoid Logic in Tests Doing so reduces the chance of introducing bugs into the test. The focus must remain on the end result, not on circumventions of implementation details. Without too many conditions, tests are also likely to be deterministic.

Can I have multiple asserts in one test?

you can have multiple asserts on the same object. they will usually be the same concept being tested.

Can a Junit test have multiple asserts?

For tests containing only a single Assertion, as is often the case, this is not an issue. But for tests that include multiple Assertions, you are limited in that your test will not run any Assertions after the first Assertion failure. The problem becomes more pronounced the more Assertions your test contains.

Why is it best practice to only have one assertion in a test?

“One assertion per test” is a wise rule to keep in mind, because it helps you have tests that fail for a specific reason, and drives you to focus on a specific behavior at a time.


1 Answers

I agree with all the other comments that it is fine to do so, if you are logically testing one thing.

There is however a difference between have many assertions in a single unit test than having a separate unit test for each property. I call it 'Blocking Assertions' (Probably a better name out there). If you have many assertions in one test then you are only going to know about a failure in the first property that failed the assertion. If you have say 10 properties and 5 of them returned incorrect results then you will have to go through fixing the first one, re-run the test and notice another one failed, then fix that etc.

Depending on how you look at it this could be quite frustrating. On the flip side having 5 simple unit tests failing suddenly could also be off putting, but it might give you a clearer picture as to what have caused those to fail all at once and possibly direct you more quickly to a known fix (perhaps).

I would say if you need to test multiple properties keep the number down (possibly under 5) to avoid the blocking assertion issue getting out of control. If there are a ton of properties to test then perhaps it is a sign that your model is representing too much or perhaps you can look at grouping properties into multiple tests.

like image 112
aqwert Avatar answered Sep 23 '22 01:09

aqwert