January 17, 2012

Mocking with JodaTime's DateTime and Google Guava's Supplier

 

Introduction

If you're a seasoned unit tester, you've learned to take note when you see any code working with time, concurrency, random, persistence and disc I/O.

The reason for this is that tests can be very brittle and sometimes down-right impossible to test properly. This post will show how to abstract out "time" by injecting a replacement for it in the consumer.  This post will be using Spring 3 as the Dependency Injection container, though Guice, other DI containers or constructor/setters on POJOs would work as well.  I will also ignore Locales since the focus is on the injection of the DateTime, not DateTime itself.

Existing code

You've been handed a piece of code to unit test (or you are creating one and this is your first stab at it).  Our first piece of code, only one class: (This class is a Spring 3.1 controller and the purpose is to return back the current time as a String)

@Controller
@RequestMapping(value = "/time")
@VisibleForTesting
class TimeController {

    @RequestMapping(value = "/current", method = RequestMethod.GET)
    @ResponseBody
    public String showCurrentTime() {
        // BAD!!! Can't test
        DateTime dateTime = new DateTime();
        return DateTimeFormat.forPattern("hh:mm").print(dateTime);
    }
}

Take note that the class does a "new DateTime()" in the class.  Here is the corresponding test class:

What happens when we run the test?  How about assuming we have a very slow machine. You could (and most likely will) end up with your comparison DateTime to be different than the returned DateTime. This is a problem!

First thing to do is to remove the dependency, but how are we going to do this?  If we make the DateTime a field on the class, we will still have the same problem.  Introduce Google Guava's Supplier interface.


Google Guava Supplier

The Supplier interface only has one method, "get()" which will return an instance of whatever the supplier is setup for.  An example, the supplier will return a user's first name if they have logged in, and a default one if they have not:

public class FirstNameSupplier implements Supplier<String> {

    private String value;
    private static final String DEFAULT_NAME = "GUEST";

    public FirstNameSupplier() {
        // Just believe that this goes and gets a User from somewhere
        String firstName = UserUtilities.getUser().getFirstName();
        // more Guava
        if(isNullOrEmpty(firstName)) {
            value = DEFAULT_NAME;
        } else {
            value = firstName;
        }
    }

    @Override
    public String get() {
        return value;
    }
}

To your implementing method, you don't care what the first name is, only that you get one.


Refactoring out DateTime

Let's move on. For a much more real example of using a Supplier (and the point of this post) let's implement a DateTime supplier to give us back the current DateTime. While we're at it, let's also create an interface so that we can create mock implementations for testing:

public interface DateTimeSupplier extends Supplier<DateTime> {
    DateTime get();
}

and an implementation:

public class DateTimeUTCSupplier implements DateTimeSupplier {
    @Override
    public DateTime get() {
        return new DateTime(DateTimeZone.UTC);
    }
}

Now we can take the DateTimeUTCSupplier and inject that into our code that needs the current DateTime as the DateTimeSupplier interface:

@Controller
@RequestMapping(value = "/time")
@VisibleForTesting
class TimeController {

    @Autowired
    @VisibleForTesting
    // Injected DateTimeSupplier
    DateTimeSupplier dateTime;

    @RequestMapping(value = "/current", method = RequestMethod.GET)
    @ResponseBody
    public String showCurrentTime() {
        return DateTimeFormat.forPattern("hh:mm").print(dateTime.get());
    }
}

In order to test this, we'll need to create a MockDateTimeSupplier and have a controller to pass in the specific instance we want to return:

public class MockDateTimeSupplier implements DateTimeSupplier {

    private final DateTime mockedDateTime;

    public MockDateTimeSupplier(DateTime mockedDateTime) {
        this.mockedDateTime = mockedDateTime;
    }

    @Override
    public DateTime get() {
        return mockedDateTime;
    }
}

Notice that the object being saved is passed in via the constructor. This will not get you the current date/time back, but will return back the specific instance you want

and finally our test that exercises (slightly) the TimeController we implemented above:

public class TimeControllerTest {

    private final int HOUR_OF_DAY = 12;
    private final int MINUTE_OF_DAY = 30;

    @Test
    public void testShowCurrentTime() throws Exception {
        TimeController controller = new TimeController();
        // Create the mock DateTimeSupplier with our known DateTime
        controller.dateTime = new MockDateTimeSupplier(new DateTime(2012, 1, 1, HOUR_OF_DAY, MINUTE_OF_DAY, 0, 0));

        // Call our method
        String dateTimeString = controller.showCurrentTime();

        // Using hamcrest for easier to read assertions and condition matchers
        assertThat(dateTimeString, is(String.format("%d:%d", HOUR_OF_DAY, MINUTE_OF_DAY)));
    }

}

Conclusion

This post has shown how to use Google Guava's Supplier interface to abstract out a DateTime object so you can better design your implementations with unit testing in mind! Suppliers are a great way to solve the "just give me something", mind you it's a known type of something.


Good luck!

37 comments:

eneveu said...
This comment has been removed by the author.
eneveu said...

[trying again with proper HTML escaping]

Cool post.

Couldn't you avoid creating a DateTimeSupplier interface, and instead inject a Supplier directly?

@Autowired
@VisibleForTesting
Supplier<DateTime> dateTime;

In which case you wouldn't need the MockDateTimeSupplier class, and could instead do:

controller.dateTime = Suppliers.ofInstance(new DateTime(2012, 1, 1, HOUR_OF_DAY, MINUTE_OF_DAY, 0, 0));

Or would Spring's autowiring fail because it would have to inject a "generified bean" (Supplier), and the type info would be gone at runtime?

Anonymous said...

So you just described the factory pattern... no big deal

without having read the javadoc... is there more to the Supplier Interface than just being a predefined Factory interface?

Unknown said...

Hey Anonymous,

You're right, the Guava Supplier is a typed factory at it's core, but the main functionality comes from the ability to guarantee an result/object, pass and transform input and return Functions to be used within an algorithm.

This blog post is not specifically about Supplier interface, the blog post is about a possible solution to dealing with Dates and how to test them.

You've identified the pattern used here so probably know, almost all solutions involve at least one pattern from the original Gang of Four book. The point of this blog is not to show off brand new code for every solution, it is to share solutions to problems common to all developers and often times I find the simplest solution is to use existing open source software. I chose to use a Supplier instead of creating my own Factory pattern comes down to the "Don't repeat yourself" principal that every developer should know and love. Why re-write something that someone else has written and has been tested by tens of thousands of developers world wide?

I would suggest you read the JavaDocs on Supplier and while you're at it, many of the other amazing library additions to the Guava project (I use ImmutableList/ImmutableMap, Multimaps, Iterators, Strings, @VisibleForTesting, and many more)

Posting a comment with flame-tones in it does not help the community share information, nor does it foster an "ask anything, there are no stupid questions" attitude that I have tried to build. In the future, if you would like to flame a posting, please visit Facebook, Reddit or Twitter

Mike!

Anonymous said...

Jodatime provides methods to "stop" the time in the class. http://joda-time.sourceforge.n.... Constructors use the methods in this class to initialize their fields. This can be useful for unit test without modifying the original code. Method to use setCurrentMillisFixed.

Anonymous said...

Sorry I forget name of class DateTimeUtils and link http://joda-time.sourceforge.net/apidocs/org/joda/time/DateTimeUtils.html

Unknown said...

Hi Mike,
Thanks for your post and explications. I don't understand exactly which is the advantage of using Supplier interface? From my point of view the only advantage is that we can use the injection directly, we are forced to implement the get() method, if there are other advantage please light me :). Thanks.

Unknown said...

I'll try to address these separately. First, setCurrentMillisFixed with the JodaTime constructors for dates is a solution, and while it is tread safe, it isn't parallelizable for tests. If you run tests concurrently and modify the system date in one test, you can't guarantee that the test will use the system date for that specific test case. This is why I recommend creating an abstraction for dates when creating calculations. Another possible strategy would be to pass around Intervals (I'm considering writing a post on that).

@sorin cristea, the Supplier interface is a very generic factory pattern (or some would like to call it a no-argument function). The advantages would be if you wanted to maybe look up a date from a database, if it isn't a suitable date, then return a default. The possibilities are pretty much endless on what you can use the Supplier for, I tend to think of it as (from the class level) "I don't really care where/how I will be getting this, but I can expect to get a workable object"

Mike!

Unknown said...

I feel really happy to have seen your webpage and look forward to so many more entertaining times reading here. Thanks once more for all the details.


java training in bangalore
java training in bangalore

Nandhu said...

its very useful for us. more helpful. i really appreciate you to do and shared this kind of helpful blog with us.
Photoshop Training Institute in Chennai | Photoshop Training Institute in Velachery

Sarath K said...

Your blog is awesome..Your blog is explain each and every concept is good...
CorelDraw Training Institute in Chennai | Photoshop Training Institute in Chennai | CorelDraw Training Institute in Velachery

Unknown said...

You are right, blogging teaches you a lot about yourself...
Best Photoshop Training Institute in Chennai | CorelDraw Training Institute in Chennai | No.1 Photoshop Training Institute in Velachery

krish said...

Needed to compose you a very little word to thank you yet again regarding the nice suggestions you’ve contributed here.
Good Graphics Designing Training Institute in Chennai | Best Multimedia Training Institute in Velachery

Ed Sheeran said...

wow really nice and useful article and explanation are very clear so easy to understand.. thanks a lot for sharing.
Photoshop Training Institute in Chennai | Best Multimedia Training Institute in Velachery

Unknown said...

Looking for the best online casino games? Come to us at BGAOC and win now perfect internet slots Luck will smile at you immediately.

VenuBharath2010@gmail.com said...

Amazing Post. The content is very interesting. Waiting for your future updates.
Xamarin Training in Chennai
Xamarin Course in Chennai
Xamarin Training
Xamarin Course
Primavera Training in Chennai
Primavera Course in Chennai
IELTS coaching in Chennai
IELTS Training in Chennai

jorick228 said...

Interested in details on online casinos? Come to us, here is all in full details. Top online casinos in the world Best casino only at BGAOC.

service care said...

I think this is a great site to post and I have read most of contents and I found it useful for my Career .Thanks for the useful information. Good work.Keep going.
motorola service center near me
motorola mobile service centre in chennai
moto g service center in chennai

anitha said...

Excellent Post. Extra-ordinary work to share with. Thanks for posting.
Node JS Training in Chennai
Node JS Course in Chennai
Node JS Advanced Training
Node JS Training Institute in chennai
Node JS Training in Velachery
Node JS Training in Tambaram
Node JS Training in OMR
Node JS Training in T Nagar

DedicatedHosting4u said...


Just seen your Article, it amazed me and surpised me with god thoughts that eveyone will benefit from it. It is really a very informative post for all those budding entreprenuers planning to take advantage of post for business expansions. You always share such a wonderful articlewhich helps us to gain knowledge .Thanks for sharing such a wonderful article, It will be deinitely helpful and fruitful article.
Thanks
DedicatedHosting4u.com

Tech News said...

Nice blog
Visit here - BIG DATA AND HADOOP TRAINING IN BANGALORE

event management said...

Thanks for sharing this valuable information with us..
wedding photographers in chennai

Stephie John said...

Hi Thank you for such a nice article. Best website development company Keep posting.

IT Software Training Institute said...

Such a very useful article. Very interesting to read this article.I would like to thank you for the efforts you had made for writing this awesome article.

MongoDB Online Training

MongoDB Classes Online

MongoDB Training Online

Online MongoDB Course

MongoDB Course Online

IT Software Training Institute said...

Very interesting blog Thank you for sharing such a nice and interesting blog and really very helpful article.

Sap Solution Manager Online Training

Sap Solution Manager Classes Online

Sap Solution Manager Training Online

Online Sap Solution Manager Course

Sap Solution Manager Course Online

Jayalakshmi said...

Wow it is really wonderful and awesome thus it is very much useful for me to understand many concepts and helped me a lot.
angular js training in chennai

angular js training in tambaram

full stack training in chennai

full stack training in tambaram

php training in chennai

php training in tambaram

photoshop training in chennai

photoshop training in tambaram

praveen said...

Hi it's really informative blog ,
Thanks to share with us,

https://www.porurtraining.in/hardware-and-networking-training-in-porur-chennai
https://www.porurtraining.in/xamarin-training-in-porur-chennai
https://www.porurtraining.in/ios-training-in-porur-chennai
https://www.traininginvelachery.org/iot-training-in-velachery-chennai

jeni said...

i never know the use of adobe shadow until i saw this post. thank you for this! this is very helpful.
hadoop training in chennai

hadoop training in velachery

salesforce training in chennai

salesforce training in velachery

c and c plus plus course in chennai

c and c plus plus course in velachery

machine learning training in chennai

machine learning training in velachery


shiny said...

So Informative and useful blog.

web designing training in chennai

web designing training in annanagar

digital marketing training in chennai

digital marketing training in annanagar

rpa training in chennai

rpa training in annanagar

tally training in chennai

tally training in annanagar

vivekvedha said...

its very useful for us. more helpful. i really appreciate you to do and shared this kind of helpful blog with us.
acte chennai

acte complaints

acte reviews

acte trainer complaints

acte trainer reviews

acte velachery reviews complaints

acte tambaram reviews complaints

acte anna nagar reviews complaints

acte porur reviews complaints

acte omr reviews complaints


Unknown said...

its very useful for us. more helpful. I really appreciate you to do and shared this kind of helpful blog with us.
Web Development Company in Haldwani

Aishwariya said...

Amazing Post, Thank you for sharing this post really this is awesome and very useful. Primavera p6 Training Online | Primavera Training Chennai

Haris said...

I feel satisfied with your share.
It's very helpful for me and I am so happy to see your blog.
Thank you for sharing it with us. law dissertation help

kinemaster Lite said...

i was looking for this information and i finally found it on your post. thanks for sharing it with us

Anonymous said...

Last but not least, we looked on the mobile-friendliness and buyer assistance of every on-line casino. Casibee helps over half a dozen banking strategies in 점보카지노 complete. This is an honest selection, however it does not quite cowl all of the bases. If you are a crypto person, you're out of luck as a result of|as a outcome of} this on-line casino does not accept any digital currencies.

Irich photography said...

Good information

Kine App said...


I'm glad to hear that you found the information helpful and that you enjoyed visiting my blog. Thank you for taking the time to share your feedback with us. It's truly appreciated. Visit kinemasterapks.net