GWT 2.7/GwtMockito: History support

119 views
Skip to first unread message

Andreas Kohn

unread,
Feb 17, 2015, 4:13:17 AM2/17/15
to google-we...@googlegroups.com
Hi,

some of our code uses the GWT History class (specifically the #encodeHistoryToken() method), and while trying to write tests for that code I found that GwtMockito doesn't provide a specific fake for History (fair enough), but unfortunately also that providing one is quite hard: History delegates the encoding to an instance of HistoryImpl, but that one is private, and so the straight-forward way of creating and registering a fake doesn't work.

My work-around for this is to use reflection to make the class accessible enough, and that works reasonably well. But, the work-around is ugly, so I was wondering: is there any specific reason not to make HistoryImpl accessible?

Regards,
--
Andreas

PS: If it helps anyone in the same position, here's the code I'm using. This uses easymock for various reasons, using mockito should work as well:
// Prepare the private HistoryImpl class for mocking
Class<?> historyImplClass = Class.forName("com.google.gwt.user.client.History$HistoryImpl");
Method encodeHistoryTokenMethod = historyImplClass.getMethod("encodeHistoryToken", String.class);
encodeHistoryTokenMethod
.setAccessible(true);

// Create the mock itself
Object historyImpl = createNiceMock(historyImplClass);

// Mock #encodeHistoryToken()
encodeHistoryTokenMethod
.invoke(historyImpl, isA(String.class));
expectLastCall
().andAnswer(new IAnswer<String>() {
   
@Override
   
public String answer() throws Throwable {
       
String value = (String) getCurrentArguments()[0];        
       
if (value == null) {
           
throw new NullPointerException();
       
} else if (value.isEmpty()) {
           
return "";
       
}
       
       
try {
            URI uri
= new URI(null, null, value);
           
// URI#getRawFragment() is pretty close, but it does unfortunately retain characters >= U+0080,
           
// which we do not want.
           
// Instead, rely on #toASCIIString(), and cut away the '#' in the beginning.
           
// See also HistoryImpl#encodeHistoryToken()
           
String encoded = uri.toASCIIString();
           
assert encoded.charAt(0) == '#' : "Sanity";
           
return encoded.substring(1).replace("#", "%23");
       
} catch (URISyntaxException e) {
           
throw new IllegalArgumentException("Cannot use '" + value + "' as fragment", e);
       
}
   
}
}).anyTimes();
replay
(historyImpl);

// Finally, tell GwtMockito about it.
GwtMockito.useProviderForType(historyImplClass, new FakeProvider<Object>() {
   
@Override
   
public Object getFake(Class<?> type) {
       
return historyImpl;
   
}
});



Jens

unread,
Feb 17, 2015, 5:19:54 AM2/17/15
to google-we...@googlegroups.com

 is there any specific reason not to make HistoryImpl accessible?

 Its good practice for a library to hide implementation details because you do not want developers to accidentally or knowingly depend on these details. I am pretty sure other features of GwtMockito look equally "ugly", because GwtMockito itself is a huge hack to circumvent GWT.create() and JSNI in unit tests.

Often you can also just refactor your app a bit so it does not directly call such GWT methods. You can wrap them in helper classes like HistoryTokenEncoder that you can swap out during testing or wrap them using a helper method that can be overwritten in tests (anonymously or by sub classing)

-- J.

Andreas Kohn

unread,
Feb 24, 2015, 12:45:57 PM2/24/15
to google-we...@googlegroups.com
Hi,

Thanks for your answer. I'll look into putting a layer between GWT (at least the parts that use static helpers) and our application code then. For now the ugliness works, and I guess I'll be able to use it also in similar other situations.

Regards,
--
Andreas
Reply all
Reply to author
Forward
0 new messages