3

How to write unit testing for following code block where the

public class Hotel
{
    DateTime CLOSING_TIME // imported from somewhere else
    public bool IsOpen
    {
        get
        {
            return DateTime.Now <= CLOSING_TIME
        }
    }
}

I tried following either one will fail all the time, how can i make sure that both unit test case will pass every time:

[TestFixture]
public void ShouldBeOpen()
{
    var Hot= new Hotel();
    Assert.True(Hot.IsOpen); 
}

[TestFixture]
public void ShouldBeOpen()
{
    var Hot= new Hotel();
    Assert.False(Hot.IsOpen); 
}

Can someone please help?

Matěj Štágl
  • 870
  • 1
  • 9
  • 27
  • 5
    Possible duplicate of [What's a good way to overwrite DateTime.Now during testing?](https://stackoverflow.com/questions/43711/whats-a-good-way-to-overwrite-datetime-now-during-testing) – FCin Jul 26 '18 at 12:44
  • 1
    you should consider to only use the `TimeOfDay` of the `DateTime` or do you update your ClosingTime daily? – Marco Forberg Jul 26 '18 at 12:56

2 Answers2

2

There are many problems with your tests:

  • They are non-deterministic (as you already noticed) because they rely on time
  • They are redundant (the second test tests exactly the same as the first one)
  • From the semantic you typed it looks like you need to work with time and not a full DateTime, which you fail to express in IsOpen

I also personally think that such a trivial test is useless because it adds no value over this test:

[TestFixture]
public void ShouldBeOpen()
{
    Assert.True(DateTime.Now <= CLOSING_TIME);
}

However a slight change in IsOpen can improve both your implementation and testing code and keep your code simple (no need to stub/mock the system date):

public sealed class Hotel
{
    public bool IsOpenAt(DateTime time) => time.TimeOfDay <= CLOSING_TIME.TimeOfDay;
}

With such kind of tests which are really testing some business assertions:

[TestFixture]
public void ShouldBeOpen()
{
    Assert.True(new Hotel().IsOpenAt(CLOSING_TIME));
}

[TestFixture]
public void ShouldNotBeOpen()
{
    Assert.False(new Hotel().IsOpenAt(CLOSING_TIME.AddMinutes(1)));
}

Note

There is something I'd like to address although not requested: CLOSING_TIME. This field looks suspicious to me. Indeed, I suspect the way you set this value introduces unnecessary coupling from something with Hotel. If not the case, I would advocate that you request this value as a constructor's parameter to achieve a better decoupling:

public sealed class Hotel
{
    private readonly DateTime _closingTime;
    public Hotel(DateTime closingTime) => _closingTime = closingTime;
    public bool IsOpenAt(DateTime time) => time.TimeOfDay <= _closingTime.TimeOfDay;
}
Spotted
  • 4,021
  • 17
  • 33
0

The code below I threw together to mock a time interface that you can use for your unit tests. using this you can set your time to either the real time or a fake time that you specify. I often use this method when unit testing. It's called Dependency injection or constructor injection which is very useful for unit testing.

    class Hotel
    {
        public DateTime ClosingTime = DateTime.ParseExact("17:00:00", "HH:ii:ss", CultureInfo.InvariantCulture);
        public IStubClock Clock;
        public bool IsOpen
        {
            get
            {
                return Clock.Now.TimeOfDay <= ClosingTime.TimeOfDay;
            }
        }
        public Hotel(IStubClock clock)
        {
            Clock = clock;
        }
    }

Using this interface you can mock any DateTime.Now structure

    public interface IStubClock
    {
        DateTime Now { get; }
    }

A fake variant

    public class FakeClock : IStubClock
    {
        private DateTime _now;
        public DateTime Now
        {
            get
            {
                return _now;
            }
        }

        public FakeClock(DateTime now)
        {
            _now = now;
        }
    }

And a real variant

    public class RealClock : IStubClock
    {
        public DateTime Now
        {
            get
            {
                return DateTime.Now;
            }
        }
    }

and then you can use them in your tests doing something like this

    class Program
    {
        static void Main(string[] args)
        {
            IStubClock fakeClock = new FakeClock(new DateTime(1, 1, 1, 10, 0, 0)); //time is set to 10am
            IStubClock realClock = new RealClock(); //time is set to whatever the time now is.
            Hotel hotel1 = new Hotel(fakeClock); //using fake time
            Hotel hotel2 = new Hotel(realClock); //using the real time
        }
    }
Dan Scott
  • 554
  • 3
  • 10