Showing posts with label jvm. Show all posts
Showing posts with label jvm. Show all posts

Thursday, January 6, 2011

Easy unit testing for Android

Testing and Mocking

Everyone who is, like me, 'spoiled'  by mocking frameworks like Mockito, EasyMock, etc know the pain of trying to write compact and readable unit tests for Android. Of course there is JUnit built-in support in Android that offers the whole battery of base classes in order to perform integration testing. And, surely, there is a number of base mock classes that are supposed to allow proper unit testing. However when it comes to writing the tests those mock classes are not really 'nice' mocks. They are not really that 'nice' because they do stuff like:

/**
 * A mock {@link android.content.Context} class.  All methods are non-functional and throw 
 * {@link java.lang.UnsupportedOperationException}.  You can use this to inject other dependencies,
 * mocks, or monitors into the classes you are testing.
 */
public class MockContext extends Context {

    @Override
    public AssetManager getAssets() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Resources getResources() {
        throw new UnsupportedOperationException();
    }
...

}

As you can see it throws exceptions on every invocation, which means that if you want to use it you have to extend it. Problem is that it almost never limited by overriding just one method. And because your test cases are probably different the re-use of the extended MockContext is pretty limited and, again, if you try to create a re-usable MockContext the amount of effort will be somewhat close to re-writing the platform classes! So to go on with it you might start creating a number of subclasses of MockContext's to serve your particular needs. By the way, this is exactly what all the built-in subclasses of the MockContext do - even though they are a part of the public API if you have a closer look at them you might notice those are written for some particular cases to test the Android platform itself! But even those are not generic enough as you can see in this example:

RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(
                new MockContext2(), // The context that most methods are delegated to
                getContext(), // The context that file methods are delegated to
                filenamePrefix);

As you can see all sorts of compositions, extensions happen a lot.

Mockito to the rescue?

"Use a mocking framework" - many will probably think. Well, it's not as simple. The problem is that Dalvik VM is not really a Java VM. And most known pure-java mocking frameworks wont work out of the box, e.g. http://code.google.com/p/mockito/issues/detail?id=57
Some mocking frameworks require interfaces, which Android isn't really full of (for good reasons given the circumstances). Some mocking frameworks can do more than others and some have more or less ease of use.
Besides, if you base your classes on Android base TestCases it will run only on the emulator which isn't fast enough for unit testing.

So use the standard Java VM?

Right, the mocking frameworks will shine again! Not as simple again, you would need to mock every single call to classes contained in the Android platform (many of which are static!) which is probably even worse than just extending Android built-in mock classes.
Well, some (even people at Google) try to do it, but when I tried to repeat it with the real code I just gave up on the amount of work needed to be done just to perform simple testing.

Stumbled upon


And then I stumbled upon Robolectric. Robolectric is capable of either creating 'nice' stubs that don't throw RuntimeException("Stub!") (which is what happens if you use classes from distributable android.jar) or delegating calls to the classes in the Android platform. Basically, you can run unit tests outside the emulator in super-fast Java VM with all the mocking frameworks you like!

This allowed me to use my favorite Mockito without the need of having a separate test project, I simply employed maven!

There are plenty of articles on the Internet on how to mavenize android projects. My setup isn't much different from those so I will just focus on further customization. Quickly, I enabled dependencies to run Robolectric and Mockito in the test phase:


        
            com.pivotallabs
            robolectric
            0.9.5
            test
        
        
            junit
            junit
            4.8.2
            test
        
        
            org.mockito
            mockito-all
            1.8.5
            test
        
        
            com.google.android
            android-test
            test
            2.2.1
        
        
            com.google.android
            android
            2.2.1
            provided
        
    

If you notice I use a version of JUnit somewhat newer than the one bundled with Android SDK.
When we want to write a test case we annotate it with the Robolectric runner which does all the magic of running the unit test code on regular JVM:

@RunWith(RobolectricTestRunner.class)
public class HudsonViewProcessorUnitTest {

Creating Mockito mocks then is nothing but the usual:

@Before
    public void setUp() {
        contextMock = mock(Context.class);
        parserMock = mock(ViewJsonParser.class);
        hudsonStatusDaoMock = mock(HudsonStatusDAO.class);
        hudsonJobDaoMock = mock(HudsonJobDAO.class);
    }

Note, that I create a mock of the regular Context class in Android. And since Mockito creates 'nice' mocks by default in my particular case I didn't have to stub much of it!

Creating a test case is also very straight forward:

@Test
    public void testSimple() throws BuildParsingFailedException {
        HudsonViewProcessor hudsonViewProcessor = new HudsonViewProcessor(contextMock, defaultStatusBean, true);
        injectMocks(hudsonViewProcessor);
        List<HudsonJobBean> hudsonJobs = createHudsonJobs(WIDGET_ID, "blue", "yellow", "red");

        when(parserMock.parseHudsonJobs()).thenReturn(hudsonJobs);

        ...
    }

You might notice I don't use dependency injection which is, again, not supported by Android by-default. So I pass dependencies either via constructor or via setter methods (I have used both ways in this example) which allows me to reduce discomfort of not having dependency injection and still having the code testable.

My next stop would be to have a look at RoboGuice and see if dependency injection is usable and fast enough on Android after JIT was introduced.

The combination of the two frameworks Rolectric and Mockito made unit testing on Android fun again!
Published with Blogger-droid v1.6.5

Wednesday, August 12, 2009

A trip to... temp files...

Who would have thought that temp files deserved my attention so that I actually blog about it, yet this topic can be... not unimportant :)

Anyways... thinking of portability of java apps some of past projects came to my mind where I needed to run Orbeon app with JBoss on Mac OS-X. Seems to be no big deal right? Well... behold!

The thing is... the app uses temp files according to java.io.File.createTempFile() which seem to be not a big deal but it kinda gets in your way when you try to load the file back via URLConnection and some (probaly URL) decoding of the file path has happen on the way. Mostly (in this case) this goes unpunished... While this is obviously bug in the application that's not the point...

What goes wrong? What does go wrong is that the jvm on Mac OS-X uses the default temp dir which contains several '+' characters (?!) in the path, e.g.:

/var/folders/gL/gLHrVGaAFdSBQbeQakQSnU+++TM/-Tmp-/


This is actually easy to 'fix' (besides fixing the code, of course :) by overriding the default temp dir location and feeding -Djava.io.tmpdir variable to the JVM but still the situation was amused me for some reason. I am wondering if anyone knows the reason behind such creativity?

Thus, a word of caution: watch out for temp files on OS-X :)