2

I am following along with the TDD Kata 'Greeter', wherein I have to create a class with a method which returns a different output depending on the time of day it was called. So I have this class Greeter

public class Greeter : Greeter
{
    public DateTime time
    {
        get
        {
            return DateTime.Now;
        }
    }

    public Greeter() { }

    public string greet(string name)
    {
        string greeting = "Hello ";

        name = name.Substring(0, 1).ToUpper() + name.Substring(1);

        if(name.Length > 36)
        {
            name = name.Substring(0, 35);
        }

        if(time.Hour >= 6 && time.Hour <= 12)
        {
            greeting = "Good morning";
        } else if (time.Hour >= 18 && time.Hour <= 22)
        {
            greeting = "Good evening";
        } else if (time.Hour >=22 || time.Hour < 6)
        {
            greeting = "Good night";
        }

        return $"{greeting} {name}";
   }

}

Greeter implements IGreeter

    public interface IGreeter
    {
        DateTime time
        {
            get;
        }
        string greet(string name);
    }

Now on the unit testing side of things, I have to test given different times of day to see the response: Good morning ~ in the AM, Good evening ~ in the evening, Good night ~ at night, and Hello ~ in the default case.

So my question is, how do I design these unit tests with these time constraints in mind without too much modifying of the class? From what I've been told, changing the class just to enable unit tests is considered code smell. Is there a way I can achieve this using NUnit and Moq? The best solution I've seen so far is to DI a time provider in the constructor, but I'd rather not have to supply the generic time provider in every instantiation just to enable the tests.

Any guidance would be greatly appreciated.

TheBatman
  • 770
  • 4
  • 10
  • Its a dupe but they said the answers to that one don't work for their requirements – DetectivePikachu May 01 '19 at 15:59
  • how come the SO UI doesn't show the link to the dupe? I'm having the same issue with this kata – greenfeet May 03 '19 at 13:15
  • Just for the record, depending on time is called Temporal Coupling. It is possible - for example - to inject a function Now() DateTime. Now we can provide a specific time when testing. Reference: Keep it DRY, Shy, And tell the other guy http://media.pragprog.com/articles/may_04_oo1.pdf – Kaveh Shahbazian May 05 '19 at 21:04

2 Answers2

1

Just pass in a DateTime object when you call your greet method. Example

greeter.greet(DateTime.Now, "steve");

It will remove your dependency on DateTime.Now in your time method. Then you can easily unit test it.

Rob Rader
  • 11
  • 2
0

You should get the call to the DateTime class out of your code. Use an interface here and provide the implementation from the outside.

public interface IDateTime
{
    DateTime Now();
}

public class Foo
{
    IDateTime dt;

    public DateTime GetActualTime
    {
        get
        {
            return dt.Now();
        }
    }

    public Foo(IDateTime dt)
    {
        this.dt = dt;
    }

    public string Bar()
    {
        return this.GetActualTime().ToString();
   }
}

For testing, so not using Moq, we implement a specific implementation.

public class DateTimeTest : IDateTime
{
    DateTime dt;
    public DateTimeTest(DateTime testTime)
    {
        this.dt = testTime;
    }

    public DateTime Now()
    {
        return dt;
    }
}

and use it in the test

// Set your test time
DateTimeTest testTime = new DateTime(2019, 5, 1, 18, 04, 30);

// Create your sut
foo sut = new Foo(testTime);

// Call the function using the defined time
var result = sut.Bar();

In the production code, you then need an implementation which uses the .NET DateTime object.

See also: DateTime Interface to be used for unit testing

leflon
  • 1
  • 2