6
\$\begingroup\$

I always see comments about how a Unit Test should only be testing one piece of functionality. For the most part, I definitely agree with that assessment. However, I'm working on testing a method right now that has me pondering if being laxer on this ideal would be a better thing, so I'm curious to everyone's opinions.

The method that I'm testing is one that gathers Messages created between a user-defined date range, and groups them by Date, with totals of each type of Message per day. (Request, Offer, Dialog, and Referral).

So for a range of two days (for simplicity), I'll return an IEnumerable<MessageDay>, where MessageDay is defined as such:

public class MessageDay() {
    public DateTime Date { get; set; }
    public int Requests { get; set; }
    public int Offers { get; set; }
    public int Dialogs { get; set; }
    public int Referrals { get; set; }
    public int Total {
        get {
            return this.Requests + this.Offers + this.Dialogs + this.Referrals;
        }
    }
}

I have my Mock setup for my unit test with several of each type of Message, ranging from DateTime.MinValue, DateTime.Now, and DateTime.MaxValue.

Should I create a Unit Test with the same parameters going into the method I'm testing repeatedly? (i.e. first test will test a count of the days, second test will test the total Requests for day 1, etc for each type of Message, for each date)

I'm thinking that having so many tests (up to 13 per date range with 4 different date parameter possibilities) would be ridiculous, however I would like to know the standard, accepted way to do this.

EDIT Here is my Mock data that I have set up:

[TestInitialize()]
public void TestInitialize() {
    var mock = new Mock<IReportingRepository>();
    var now = DateTime.Now;

    mock.Setup(m => m.Messages).Returns(new Message[] {
        new Request { CreatedOn = now },
        new Request { CreatedOn = DateTime.MinValue },
        new Request { CreatedOn = DateTime.MaxValue },
        new Offer { CreatedOn = DateTime.MinValue },
        new Offer { CreatedOn = DateTime.MinValue },
        new Offer { CreatedOn = DateTime.MaxValue },
        new Dialog { CreatedOn = now },
        new Dialog { CreatedOn = DateTime.MaxValue },
        new Referral { CreatedOn = now },
        new Referral { CreatedOn = DateTime.MinValue }
    }.AsQueryable());

    controller = new ReportingController(mock.Object);
}

This would be along the lines of my "fewest number of tests":

[TestMethod]
public void CanFindAllCounts() {
    var result = (MessageResponse)controller.GetMessageAnalytics(new MessageRequest()).Data;

    Assert.AreEqual(3, result.Days.Count());

    var day = result.Days.FirstOrDefault();
    Assert.AreEqual(4, day.Total);
    Assert.AreEqual(1, day.Requests);
    Assert.AreEqual(2, day.Offers);
    Assert.AreEqual(0, day.Dialogs);
    Assert.AreEqual(1, day.Referrals);

    day = result.Days.Where(d => d.Date.Date == DateTime.Now.Date).FirstOrDefault();
    Assert.AreEqual(3, day.Total);
    Assert.AreEqual(1, day.Requests);
    Assert.AreEqual(0, day.Offers);
    Assert.AreEqual(1, day.Dialogs);
    Assert.AreEqual(1, day.Referrals);

    day = result.Days.Last();
    Assert.AreEqual(3, day.Total);
    Assert.AreEqual(1, day.Requests);
    Assert.AreEqual(1, day.Offers);
    Assert.AreEqual(1, day.Dialogs);
    Assert.AreEqual(0, day.Referrals);
}

The other extreme would of course be a test for each of those Asserts. Being that I'm using Moq, it's not a huge deal and would run quickly, but I just feel that it's overkill.

\$\endgroup\$
9
  • \$\begingroup\$ Possibly Programmers? I can imagine this question working here though, if you include the tests. \$\endgroup\$
    – Magus
    Mar 11, 2014 at 14:33
  • 1
    \$\begingroup\$ That's much better. I'd venture to say that either this is correct (you're just validating the result of a single method), or you ought to abstract it in such a way that the validation is simpler and requires a single assert; this could possibly be done by making a custom assert, or defining an .Equals() method on your Day class. \$\endgroup\$
    – Magus
    Mar 11, 2014 at 16:06
  • 2
    \$\begingroup\$ I don't think I'd consider .Equals() overkill, because it's part of System.Object. You have one, but it isn't useful for comparisons at the moment, because it only checks reference equality. It wouldn't be much of a stretch to imagine calling it in the future. \$\endgroup\$
    – Magus
    Mar 11, 2014 at 16:34
  • 1
    \$\begingroup\$ Your test could fail if TestInitialize runs just before and TestMethod runs just after midnight. Read DateTime.Now just once and use the result (or some constant value) in both setup and execution of the test. \$\endgroup\$ Mar 12, 2014 at 15:19
  • 1
    \$\begingroup\$ +1 to @Magus' Equals comment. Even more so because Equals declares something about the design of that class. It is not merely a mechanism to DRY up test code. AND of course Equals must be tested too. \$\endgroup\$
    – radarbob
    Mar 26, 2014 at 3:09

1 Answer 1

8
\$\begingroup\$

In this case, what you're really trying to do is validate three MessageDays, as the return values of one method. As you are only testing one thing (the successful return of a method), you need not worry about testing too many things. I would, however, reduce the amount of asserts to three by implementing .Equals() on MessageDay.

While it may seem like overkill, .Equals() is a virtual method on System.Object, so you do already have an implementation of it available (just not the one you need, as it is currently a reference comparison).

I can't predict whether or not you'll need it again in the future, but I don't consider that a problem when implementing the method in question, because all objects have it. The effort is small, and the chances of using it somewhere other than just testing are high.

The downside is, of course, that your test will have to declare expected values, which may mean that it becomes no shorter, and the reduced number of asserts means your test is slightly less granular. For the second point, that's probably fine, because it also frees you to redesign the class without changing the test. The length could be reduced by comparing with values saved in the test initialization.

\$\endgroup\$
1
  • 3
    \$\begingroup\$ +1 This is the cleanest and most conventional way of doing it. I advise following the guidelines for Implementing the Equals Method. Providing an override for ToString is also helpful so failed tests output something meaningful rather than just the type names. \$\endgroup\$
    – Dan Lyons
    Mar 11, 2014 at 17:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.