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:
[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?
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?
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!
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.
Sorry I forget name of class DateTimeUtils and link http://joda-time.sourceforge.net/apidocs/org/joda/time/DateTimeUtils.html
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.
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!
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
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
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
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
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
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
Looking for the best online casino games? Come to us at BGAOC and win now perfect internet slots Luck will smile at you immediately.
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
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.
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
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
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
Nice blog
Visit here - BIG DATA AND HADOOP TRAINING IN BANGALORE
Thanks for sharing this valuable information with us..
wedding photographers in chennai
Hi Thank you for such a nice article. Best website development company Keep posting.
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
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
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
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
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
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
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
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
Amazing Post, Thank you for sharing this post really this is awesome and very useful. Primavera p6 Training Online | Primavera Training Chennai
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
i was looking for this information and i finally found it on your post. thanks for sharing it with us
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.
Good information
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
Post a Comment