The benefits of externalizing commonly used utility functions to Services on their own.

As a Java developer, you have, without a doubt dealt with code that is time-related. Let’s imagine the following very common scenario: you have a user for your webapp and he recently updated his password, so you generated a password updated token and sent it to him via email so he can follow the link and reset his password. In addition to that, that password update token should be valid only a day after it has been issued by your system.

The common way to handle such requirement is to create a bean, let’s call it PasswordResetToken, like that:

@Data
@Entity
public class PasswordResetToken {
@Id private UUID tokenId;
private UUID userId;
private String passwordResetToken;
private Long issuedTimestamp;
private Long validTo;
}
view raw gistfile1.java hosted with ❤ by GitHub

This is as simple as it can get. Let’s see now the big picture. So the user makes a new password reset request, and the system creates a new instance of a PasswordResetToken for him. Here is a very simple service method that does the work:

public PasswordResetToken generatePasswordResetTokenForUser(UUID userId){
Long creationTime = System.currentTimeMillis();
PasswordResetToken token = new PasswordResetToken();
token.setUserId(userId)
.setPasswordResetToken(randomStringService.generateRandomString())
.setIssuedTimestamp(creationTime)
.setValidTo(creationTime + DateTimeConstants.MILLIS_PER_DAY);
passwordResetTokenDao.save(token);
return token;
}
view raw gistfile1.java hosted with ❤ by GitHub

This will all work, but at the moment you want to start testing that, you will begin experiencing the shortages of your approach. For example, what about a test that would want to check if the current time is set properly as the time on which the bean is issued? Or a test checking if the validTo property is exactly one day after the issuing time? Simple, you say, just mock the System.currentTimeMillis(). However, System.currentTimeMillis is a native call, and whatever mocking library you use, ( I prefer using https://code.google.com/p/mockito/, but you can check out JMockit or PowerMock as well) you would have to jump through some hoops to achieve proper mocking of that call. So how do you actually handle the behavior of static methods in your tests?

The solution is very simple: externalize the call to System.getCurrentTimeMillis() into a service on its own. That way you could easily swap the implementation with another mocked implementation, even if you don’t want to include a third library like Mockito in your JUnit tests. Here is an example of such a service, let’s call it TimeService, and its implementation:

public interface TimeService {
Long getCurrentTime();
}
@Component
public class TimeServiceImpl implements TimeService {
@Override
public Long getCurrentTime(){
return System.currentTimeMillis();
}
}
view raw gistfile1.java hosted with ❤ by GitHub

Then in your unit test you should be able to do something like that:

@Component
@Accessors(chain=true)
public class ResetPasswordTokenServiceImpl implements ResetPasswordTokenService {
@Inject @Setter private RandomStringService randomStringService;
@Inject @Setter private TimeService timeService;
@Inject @Setter private PasswordResetTokenDao passwordResetTokenDao;
@Override
public PasswordResetToken generatePasswordResetTokenForUser(UUID userId){
// instead of using System.currentTimeMillis(), we externalize the call to a service, so we can track it easily
Long creationTime = timeService.getCurrentTime();
PasswordResetToken token = new PasswordResetToken();
token.setUserId(userId)
.setPasswordResetToken(randomStringService.generateRandomString())
.setIssuedTimestamp(creationTime)
.setValidTo(creationTime + DateTimeConstants.MILLIS_PER_DAY);
passwordResetTokenDao.save(token);
return token;
}
}
public class ResetPasswordTokenTest {
private ResetPasswordTokenService resetPasswordTokenService;
private RandomStringService randomStringService;
private TimeService timeService;
private PasswordResetTokenDao passwordResetTokenDao;
@Before
public void setup(){
// use mocks like me or create your own mocked implementations
randomStringService = mock(RandomStringService.class);
timeService = mock(TimeService.class);
passwordResetTokenDao = mock(PasswordResetTokenDao.class);
resetPasswordTokenService = new ResetPasswordTokenServiceImpl()
.setRandomStringService(randomStringService)
.setTimeService(timeService)
.setPasswordResetTokenDao(passwordResetTokenDao);
}
@Test
public void testResetPasswordTokenHasCorrectIssuedTimestampAndValidTo(){
UUID userId = UUID.randomId();
Long currentTime = System.currentTimeMillis();
// make the mocked service to always return the currentTime argument
when(timeService.getCurrentTime()).thenReturn(currentTime);
ResetPasswordToken token = resetPasswordTokenService.generatePasswordResetTokenForUser(userId);
// now we can easily make assertions about the issued time and the valid to
assertEquals(currentTime, token.getIssuedOn);
assertEquals(currentTime + DateTimeConstants.MILLIS_PER_DAY, token validTo);
}
}
view raw gistfile1.java hosted with ❤ by GitHub

What you have achieved is neater, more easily testable code. The approach of externalizing helper static functions to services on their own can be applied wherever we have ‘interesting data’ being set. Interesting data is all data that specifies behavior; in our example, validTo would maybe be used to determine if the password token is still valid, and if not, it can be discarded. That, of course is a very naive example, but you should be easily be able to see the biggest benefits:

  • Avoids static methods, which makes testing easier. If the greatest Java mocking library (Mockito) doesn’t offer help for mocking static methods out of the box, maybe it is worth your while to think why you shouldn’t use them that much either.
  •  Smallest details of your code that can affect behavior are now tested.

One thought on “The benefits of externalizing commonly used utility functions to Services on their own.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.