11

I use JodaTime#DateTime, and I need to mock its behavior. Since it is not possible to directly mock JodaTime#DateTime, I create an interface of it

Clock.java

public interface Clock {
    DateTime getCurrentDateTimeEST();
    DateTime getFourPM_EST();
    DateTime getSevenPM_EST();
}

JodaTime.java

public class JodaTime implements Clock {

    @Override
    public DateTime getCurrentDateTimeEST() {
        return new DateTime(DateTimeZone.forID("EST"));
    }

    @Override
    public DateTime getFourPM_EST() {
        DateTime current = getCurrentDateTimeEST();
        return new DateTime(current.getYear(), current.getMonthOfYear(), 
                current.getDayOfMonth(), 16, 0, 0, 0, DateTimeZone.forID("EST"));
    }

    @Override
    public DateTime getSevenPM_EST() {
        DateTime current = getCurrentDateTimeEST();
        return new DateTime(current.getYear(), current.getMonthOfYear(), 
                current.getDayOfMonth(), 19, 0, 0, 0, DateTimeZone.forID("EST")); 
    }   
}

Here is the method that I want to test

public class PrintProcessor{

  Clock jodaTime;

  public PrintProcessor(){
      jodaTime = new JodaTime();
  }
  ...
  public String getPrintJobName(Shipper shipper){
    String printJobName = null;
    //Get current EST time
    if(jodaTime.getCurrentDateTimeEST().isBefore(jodaTime.getFourPM_EST()) ||
            jodaTime.getCurrentDateTimeEST().isAfter(jodaTime.getSevenPM_EST())){   //Before 4PM EST and after 7PM EST
        switch(shipper){
        case X:
        ...
    }else if(jodaTime.getCurrentDateTimeEST().isBefore(jodaTime.getSevenPM_EST())){ //Between 4PM-7PM EST
        switch(shipper){
        case X:
        ... 
    }
    return printJobName;
  }
}

As you can see the printJobName depend on the current time of the day relative to the time interval [4PM-7PM] EST and the Shipper name. Since Shipper will be pass via parameter, we can unit test it no problem. But I need to mock the time. So here is what I try

@Test
public void testGetPrintJobNameBeforeFourPM(){
    DateTime current = new DateTime(DateTimeZone.forID("EST"));
    Clock clock = mock(Clock.class);
    //Always return 6pm when I try to ask for the current time
    when(clock.getCurrentDateTimeEST()).thenReturn(new DateTime(current.getYear(), current.getMonthOfYear(), 
            current.getDayOfMonth(), 18, 0, 0, 0, DateTimeZone.forID("EST")));
    //Test for Fedex
    String printJobName = printProcessor.getPrintJobName(Shipper.X);
    assertEquals("XNCRMNCF", printJobName);
}

The test should fail since I pass in 6PM, but XNCRMNCF is the name for before 4PM. Do I need to mock printProcessor as well. If what I have is wrong. How should I fix it? I am trying to learn writing high level java code, please be very criticized about my code. I really want to learn

Thang Pham
  • 38,125
  • 75
  • 201
  • 285

3 Answers3

5

This is a classic case of testing showing up a potential flaw in design. You cannot mock JodaTime because you have a hard-wired dependency to these classes in your class-under-test.

Have a look at the SOLID principles to understand why this could be a problem (especially in this case the Dependency Inversion Principle). If you injected JodaTime somewhere as a dependency, then in your unit test you would be able to replace a real instace of it with a mock, stub or spy as appropriate.

However: JodaTime is something that is highly unlikely to be injected with anything else in the production environment, no matter how long it is live for. Instead, in this case you would probably be better served with the Composed Method Design Pattern. Here, you would extract whatever calculation/algorithm you use to generate the printjobName to another method (I can't see how you do it here because your code snippet never assigns a value to that variable). Then you can spy (partial mock) your class under test to only mock that method and return a fixed value, regardless of the real date time that JodaTime is delivering, for instance:

public class PrintProcessor {
    ...
    public String getPrintJobName(Shipper shipper) {
        String printJobName = null;
        String timeHash = this.getTimeHash();
        if (this.isBeforeFourPM()) {
            switch(shipper) {
                printJobName = // Do something with timeHash to generate name
            }
        } else {
            ...
        }
        return printJobName;
    }

    public boolean isBeforeFourPM() {
        return (jodaTime.getCurrentDateTimeEST().isBefore(jodaTime.getFourPM_EST()) ||
            jodaTime.getCurrentDateTimeEST().isAfter(jodaTime.getSevenPM_EST()));
    }

    public String getTimeHash() {
        ... // Do something to hash the time value in to a String
    }
}

Now you can write in your test:

@Test
public void testGetPrintJobNameBeforeFourPM() {
    PrintProcessor concretePrintProcessor = new PrintProcessor();
    PrintProcessor printProcessor = spy(concretePrintProcessor);
    doReturn(true).when(printProcessor).isBeforeFourPM();

    String printJobName = printProcessor.getPrintJobName(Shipper.X);

    assertEquals("XNCRMNCF", printJobName);
}
Alan Escreet
  • 3,499
  • 2
  • 22
  • 19
  • Thank you very much. I like your answer here. – Thang Pham May 19 '11 at 16:19
  • Man. In this case, then I dont even need to create an interface of JodaTime at all, hah. Great solution. Thank you. – Thang Pham May 19 '11 at 19:51
  • Actually, this is a more of a "classic case" showing the limitations of some mocking tools, rather than any design issue in the code to be tested. In fact, instantiating `JodaTime` and assigning it to a `Clock` field in the constructor of the client class seems quite natural here. There are mocking APIs (including PowerMock and one other which I created) that *can* mock a class like `JodaTime` in a situation like this. – Rogério Jul 10 '12 at 20:56
  • @Rogerio I disagree. new JodaTime() does the job of creating a hard dependency. While mocking frameworks like PowerMock can do this, have a look at how they're implemented. They mess around with the bytecode. The end result is that although they're good and useful, they don't actually test the code that you want tested. They've already modified it. Usually this is not a problem, but I wouldn't advise using PowerMock for anything other than allowing you to put legacy code under test. If you're using it for new code, even the PowerMock developers will tell you you're doing it wrong. – Alan Escreet Jul 20 '12 at 12:12
3

You are never giving the PrintProcessor your mock. Making a mock of the object is not the same as giving your mock to an object. So, when you call methods on PrintProcessor, it is operating on a real instance of JodaTime. There are couple of ways to give the PrintProcessor your mock:

  1. Use PowerMockito (make sure you use PowerMock-mockito jar and not PowerMock-easymock jar) and mock the JodaTime constructor to return your mocked Clock object whenNew(JodaTime.class).withNoArguments().thenReturn(mockJodaTime); This will insert your mock wherever a no-arg constructor for JodaTime is used. Note: This will require you to use a mock of the JodaTime class.
  2. Add a setter method for the Clock jodaTime field (which, if only defined in the constructor should probably be final).
  3. Use a Factory for your Clock class and simply return the mock during tests (you can use PowerMockito to mock static methods).
  4. Create a constructor with a Clock parameter and pass in your mock.
pickypg
  • 22,034
  • 5
  • 72
  • 84
  • Thank you. I will probably stick with Mockito for now. For either option `2` and `4`, I have a question for you. It might be a bit stupid. In JodaTime, to get the current DateTime, you do DateTime dateTime = new DateTime(). But what happen if I dont use this dateTime object right away. Let say I have a process that took 5 minutes run. If I compare the time after this long process, will be time be off by 5 minutes, somehow dateTime still reflect the correct time? – Thang Pham May 19 '11 at 14:31
  • 1
    @Harry Pham In the code listed up top, you instantiate the `DateTime` objects on demand per method call, which is the right thing to do (and what the interface is implicitly requesting). This will result in the expected time appearing (so, 5 minutes later, you will see the proper time). If you are really only instantiating a single instance of `DateTime` for each `JodaTime` then you will face a problem. – pickypg May 19 '11 at 14:35
1

I think you're definitely on the right path. Creating the Clock interface for mocking is definitely a good idea.

One thing I don't see in your code: injecting the mocked clock into the printProcessor. After creating the mock, I think you need something along the lines of:

printProcessor.setClock(clock)

(this goes before you call getPrintJobName. This setter should set the jodaTime property in your PrintProcessor class)

I'm used to EasyMock, so I might be wrong, but I'm pretty sure you will also need to set expectations for the getFourPM_EST and getSevenPM_EST calls.

Peter
  • 870
  • 6
  • 20
  • Thank you. This question might be a bit stupid. In JodaTime, to get the current DateTime, you do `DateTime dateTime = new DateTime()`. But what happen if I dont use this `dateTime` object right away. Let say I have a process that took 5 minutes run. If I compare the time after this long process, will be time be off by 5 minutes, somehow `dateTime` still reflect the correct time? – Thang Pham May 19 '11 at 13:21