Mockito: How to mock an interface of JodaTime

前端 未结 3 1031
时光说笑
时光说笑 2020-12-09 06:06

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<

相关标签:
3条回答
  • 2020-12-09 06:25

    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.
    0 讨论(0)
  • 2020-12-09 06:32

    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);
    }
    
    0 讨论(0)
  • 2020-12-09 06:32

    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.

    0 讨论(0)
提交回复
热议问题