DevOps Zone is brought to you in partnership with:

Trisha has developed Java applications for a range of industries, including finance, manufacturing and non-profit, for companies of all sizes. She has expertise in Java high performance systems, is passionate about enabling developer productivity, and right now is getting to grips with working in an Open Source fashion as a developer for MongoDB Inc, where she contributes to the Java driver and Morphia. Trisha blogs regularly on subjects that she thinks developers and other humans should care about, she’s a leader of the Sevilla Java & MongoDB User Groups, a key member of the London Java Community and a Java Champion - she believes we shouldn't all have to make the same mistakes again and again. Trisha is a DZone MVB and is not an employee of DZone and has posted 69 posts at DZone. You can read more from them at their website. View Full User Profile

Spock Passes the Next Test - Painless Stubbing

07.11.2013
| 3494 views |
  • submit to reddit

In the last post I talked about our need for some improved testing tools, our choice of Spock as something to spike, and how mocking looks in Spock.

As that blog got rather long, I saved the next installment for a separate post.

Today I want to look at stubbing.

Stubbing
Mocking is great for checking outputs - in the example in the last post, we're checking that the process of encoding an array calls the right things on the way out, if you like - that the right stuff gets poked onto the bsonWriter.

Stubbing is great for faking your inputs (I don't know why this difference never occurred to me before, but Colin's talk at Devoxx UK made this really clear to me). 

One of the things we need to do in the compatibility layer of the new driver is to wrap all the new style Exceptions that can be thrown by the new architecture layer and turn them into old-style Exceptions, for backwards compatibility purposes.  Sometimes testing the exceptional cases is... challenging.  So I opted to do this with Spock.

class DBCollectionSpecification extends Specification {
    private final Mongo mongo = Mock()
    private final ServerSelectingSession session = Mock()
 
    private final DB database = new DB(mongo, 'myDatabase', new DocumentCodec())
    
    @Subject
    private final DBCollection collection = new DBCollection('collectionName', database, new DocumentCodec())
 
    def setup() {
        mongo.getSession() >> { session }
    }
 
    def 'should throw com.mongodb.MongoException if rename fails'() {
        setup:
        session.execute(_) >> { throw new org.mongodb.MongoException('The error from the new Java layer') }
 
        when:
        collection.rename('newCollectionName');
 
        then:
        thrown(com.mongodb.MongoException)
    }
}

So here we can use a real DB class, but with a mock Mongo that will return us a "mock" Session.  It's not actually a mock though, it's more of a stub because we want to tell it how to behave when it's called - in this test, we want to force it to throw an org.mongodb.MongoException whenever execute is called.  It doesn't matter to us what get passed in to the execute method (that's what the underscore means on line 16), what matters is that when it gets called it throws the correct type of Exception.

Like before, the when: section shows the bit we're actually trying to test. In this case, we want to callrename.

Then finally the then: section asserts that we received the correct sort of Exception.  It's not enormously clear, although I've kept the full namespace in to try and clarify, but the aim is that anyorg.mongodb.MongoException that gets thrown by the new architecture gets turned into the appropriate com.mongodb.MongoException.  We're sort of "lucky" because the old code is in the wrong package structure, and in the new architecture we've got a chance to fix this and put stuff into the right place.

Once I'd tracked down all the places Exceptions can escape and started writing these sorts of tests to exercise those code paths, not only did I feel more secure that we wouldn't break backwards compatibility by leaking the wrong Exceptions, but we also found our test coverage went up - and more importantly, in the unhappy paths, which are often harder to test.

I mentioned in the last post that we already did some simple stubbing to help us test the data driver. Why not just keep using that approach? 

Well, these stubs end up looking like this:

private static class TestAsyncConnectionFactory implements AsyncConnectionFactory {
    @Override
    public AsyncConnection create(final ServerAddress serverAddress) {
        return new AsyncConnection() {
            @Override
            public void sendMessage(final List<ByteBuf> byteBuffers, final SingleResultCallback<Void> callback) {
                throw new UnsupportedOperationException();
            }
 
            @Override
            public void receiveMessage(final ResponseSettings responseSettings, final SingleResultCallback<ResponseBuffers> callback) {
                throw new UnsupportedOperationException();
            }
 
            @Override
            public void close() {
            }
 
            @Override
            public boolean isClosed() {
                throw new UnsupportedOperationException();
            }
 
            @Override
            public ServerAddress getServerAddress() {
                throw new UnsupportedOperationException();
            }
        };
    }
}

Ick.

And you end up extending them so you can just override the method you're interested in (particularly in the case of forcing a method to throw an exception).  Most irritatingly to me, these stubs live away from the actual tests, so you can't easily see what the expected behaviour is.  In the Spock test, the expected stubbed behaviour is defined on line 16, the call that will provoke it is on line 19 and the code that checks the expectation is on line 22.  It's all within even the smallest monitor's window.

So stubbing in Spock is painless.  Next!

Published at DZone with permission of Trisha Gee, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)