Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Robolectric 3.0: Mocking System.currentTimeMillis()

I'm trying to modify the value returned by System.currentTimeMillis() so I can do operations like: write something to the database, simulate waiting 5 minutes, do a query on the database (the query is dependent on when the value is written).

The code suggested in [this SO thread]:

ShadowSystemClock shadowClock = Robolectric.shadowOf(SystemClock.class);
shadowClock.setCurrentTimeMillis(1424369871446);

Does not compile since the shadowOf method was removed. Trying alternatives like:

ShadowSystemClock shadowClock = new ShadowSystemClock();
shadowClock.setCurrentTimeMillis(1424369871446);

It appears there were issues with overriding currentTimeMillis() but those issues should be fixed as of version 3.0.

I could add PowerMock to my project and use that for this case I think, but if this is do-able with Robolectric, I'd prefer that.

Update: Getting closer, but might be missing something. This code:

ShadowSystemClock shadowClock = new ShadowSystemClock();

Log("system = " + System.currentTimeMillis() + "; shadow = " + shadowClock.currentTimeMillis() + "; time from code = " + Code.getSystemTime());

shadowClock.setCurrentTimeMillis(50000000L);

Log("system = " + System.currentTimeMillis() + "; shadow = " + shadowClock.currentTimeMillis() + "; time from code = " + Code.getSystemTime());

Outputs this:

system = 1438212006879; shadow = 0; time from code = 1438212006894
system = 1438212006898; shadow = 50000000; time from code = 1438212006898

Code.getSystemTime() is calling into the codebase being tested. The method simply returns System.currentTimeMillis().

Seems like things could work if the ShadowSystemClock intercepted the calls to currentTimeMillis(). Is there a way to do that?

like image 572
Mark Avatar asked Jul 27 '15 17:07

Mark


1 Answers

Firstly, because SystemClock is a singleton class consisting entirely of static methods, there is no need to use shadowOf(). shadowOf() is only required for finding the shadows of object instances. To access shadow static methods of SystemClock, you simply call the static methods of ShadowSystemClock directly and you should never have to instantiate ShadowSystemClock with a call to new. In your test code, you should be able to dispense with the call to new and repace shadowClock.currentTimeMillis() with ShadowSystemClock.currentTimeMillis().

Secondly, and more directly related to your question regarding System.currentTimeMillis() being intercepted - under Robolectric this will happen, but only for code that has been instrumented - that is, modified as it is loaded by Robolectric's special classloader. In instrumented code, all calls to System.currentTimeMillis() are replaced with calls ShadowSystemClock.currentTimeMillis(), which is what you want.

So in order to get your test to work the way you want it, you need to make sure that the code you are testing is instrumented. Robolectric doesn't do this for all classes by default - only Android classes. Unfortunately I'm not in a position to test this for myself at the moment, but I believe that you can tell Robolectric to instrument your own application classes by specifying the package(s) in the @Config annotation - for example, if you put @Config(instrumentedPackages={ "my.application" }) on your test class or method, then that tells Robolectric to instrument all of the classes in the "my.application" package. This will in turn cause their System.currentTimeMillis() calls to get redirected to ShadowSystemClock.currentTimeMillis(), which should be what you want.

The reason this is done differently is because using Robolectric's classloader approach, it is not possible to modify classes in the java.lang.* packages as they are already loaded before you can install the instrumenting classloader. There is a longer-term idea to try and do this differently in Robolectric using an approach similar to what JMockit does, which can get around this problem. This would mean that you won't have to explicitly instrument all of the client classes. However this is pie-in-the-sky at the moment and I wouldn't hold my breath waiting for it - hopefully the above will solve your immediate problem.

Hope this helps.

like image 98
Fr Jeremy Krieg Avatar answered Sep 28 '22 19:09

Fr Jeremy Krieg