JUnit for C# Developers 8 – Obeying Demeter and Going Beyond the Tests
Last time in this series, I pulled an “Empire Strikes Back” and ended on a bit of a down note. This time around, I’d like to explore how I’ve alleviated my Law of Demeter problems, and about how fixing a code smell in my tests pushed me into a better design.
Up until now, I’ve been blogging as I go, but this one is all in the past tense — the work is done as I type this. I set out tonight with only one goal, get rid of my LOD violations, and this is where it took me.
Rethinking my Class
Recall that last time, I was passing in a database object, querying that for a collection, querying that for a cursor, and then querying the cursor for my actual database objects that I parsed and returned from the service. After a bit of trial and error and research, I decided that my service class needed to encapsulate the collection since, as best as I can tell from whatever Eclipse’s version of Intellisense is called, cursors are forward only and then you need to get another one. So, if I don’t pass in the collection at least, my service method will only work once. Fine – not thrilled about the collection.cursor.objects thing, but it’s at least pulling one LOD violation out.
I now have a handful of tests that look like this:
@Test
public void returns_room_model_with_roomName_from_database_room_key() {
String myRoomName = "Rumpus Room. Yeah, that's right. I said Rumpus Room.";
DBObject myMockDatabaseObject = mock(DBObject.class);
Mockito.when(myMockDatabaseObject.get(RoomServiceMongoImpl.ROOM_NAME_KEY)).thenReturn(myRoomName);
DBCursor myMockCursor = mock(DBCursor.class);
Mockito.when(myMockCursor.next()).thenReturn(myMockDatabaseObject).thenReturn(myMockDatabaseObject).thenReturn(null);
Mockito.when(myMockCursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
DBCollection myMockCollection = PowerMockito.mock(DBCollection.class);
Mockito.when(myMockCollection.find()).thenReturn(myMockCursor);
RoomServiceMongoImpl myService = BuildTarget(myMockCollection);
assertEquals(myRoomName, myService.getAllRooms().toArray(new Room[2])[0].getRoomName());
}
and my class became:
public class RoomServiceMongoImpl implements RoomService {
public static final String ROOM_CODE_KEY = "room_code";
public static final String ROOM_NAME_KEY = "room";
private DBCollection _collection;
public RoomServiceMongoImpl(DBCollection collection) {
_collection = collection;
}
@Override
public Collection getAllRooms() {
Collection myRooms = new ArrayList();
DBCursor myCursor = _collection.find();
while(myCursor != null && myCursor.hasNext()) {
RoomModel myModel = buildRoomModel(myCursor.next());
if(myModel != null)
myRooms.add(myModel);
}
return myRooms;
}
private RoomModel buildRoomModel(DBObject roomObject) {
Object myRoomName = roomObject.get(ROOM_NAME_KEY);
char myRoomCode = getRoomCode(roomObject.get(ROOM_CODE_KEY));
if(myRoomName != null) {
return new RoomModel(myRoomName.toString(), null, myRoomCode);
}
return null;
}
private char getRoomCode(Object myRoomCode) {
return myRoomCode != null && myRoomCode.toString() != null && myRoomCode.toString().length() > 0 ?
myRoomCode.toString().charAt(0) : 0;
}
}
A lot cleaner and more manageable following some good TDD if I do say so myself (though I may be whiffing on some finer points of the language as I’m still rusty from 2 years of mostly uninterrupted C#). I’m still not thrilled about the heavy test setup overhead, but I’ve made incremental progress.
Now, where things got interesting is in wiring this up through Spring and MongoDB. The class works in test, but I need now to figure out how to use my spring-servlet.xml to get an instance of the collection injected into my class’s constructor. I wanted to do this (1) without defining any additional code and (2) without resotring to static implementations or singletons. For (1) I’d rather leave the DB setup stuff in XML as much as possible and for (2) I try to avoid static at all costs unless there’s some compelling argument that doesn’t lean prominently on a premise of “it’s more convenient”. Static is about as flexible as a diamond.
So, here is what I did:
I discovered that I can use factory-bean and factory-method attributes to invoke instance methods on beans that I’d created, turning their return values into other beans. I also learned that “constructor-arg” is rather unfortunately named in that it actually just translates to “arguments to the method in question”. So, in the case of the mongoDatabase bean, I’m getting it from my mongo object’s getDB() method with a string parameter of “daeadlus”. On the whole, the beans above translate to new Mongo(“192.168.2.191”).getDB(“daedalus”).getCollection(“house”) being stored in the “mongoHouseCollection” bean, which I injected into my service. When I wired and fired it, it worked perfectly the first time.
So, this post has been a little thin on actual information about JUnit (really just the denouement to my last post), but there is a nugget in here for spring wireup, and, I think the most important lesson for me is that the design benefits to TDD go beyond just code. By taking my test smell seriously, I wound up with a design where I completely factored the database setup garbage out of my code, which is clearly a good thing. Now, I’ve been around the block enough times that this would have happened regardless, but it was interesting to note that making a testability/clean-code decision and sticking to my guns teased out a macroscopic design improvement.