DaedTech

Stories about Software

By

JUnit for C# Developers 7 – Law of Demeter and Temporal Mocking

Last time in this series, I posted about a good reminder of a couple of principles of good object oriented design (respecting the Law of Demeter and avoiding static method invocations as much as possible). Today, I’m going to double back on this consciously a bit to explore some more possibilities in JUnit. Don’t worry – I will fix the design in subsequent posts.

Goals

Today, I’d like to accomplish the following:

  1. Have a mock change with each invocation
  2. Mock a low of demeter violation in as little code as possible

To the Code!

If you’ve followed my last few posts, you’ve noticed that I setup MongoDB. So, logically, the next step is connecting to it with my application, and the next step after that is mocking this connection so that I can unit test the logic (well, since I’m following TDD, technically the mocking comes first). Through trial and error in a throw-away piece of code, I discovered that I could access my database as so:

public class HouseServiceMongoImpl implements HouseService {

	private Mongo _mongoConnection;
	
	private DB _database;
	
	public HouseServiceMongoImpl() {
		try {
			_mongoConnection = new Mongo( "192.168.2.191" , 27017 );
			_database = _mongoConnection.getDB("daedalus");
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (MongoException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public House getHouse() {
		DBCollection myCollection = _database.getCollection("house");
		DBCursor myCursor = myCollection.find();
				
		List myStrings = new ArrayList();
		
		while(myCursor.hasNext()) {
			myStrings.add(myCursor.next().get("room").toString());
		}
		return new HouseModel(myStrings);
	}
	
}

Notice here that I’ve inlined instantiation of my dependencies in a form bad for testability. I won’t repeat this mistake in the actual, TDD version. This was just for me to see what actually worked and provide some context for what I’m trying to do. In my real class, RoomServiceMongoImpl, I’m going to be returning a collection of room objects, rather than a house object. So, without further ado, on to the TDD. To get things started, I wrote the following test:

public class RoomServiceMongoImplTest {

	private static RoomServiceMongoImpl BuildTarget(DB database) {
		
		return new RoomServiceMongoImpl(database);
		
	}
		
	public static class getAllRooms {
		
		/*
		 * If the driver gives us back nothing for this collection, return an empty collection to clients
		 */
		@Test
		public void returns_empty_list_when_database_get_collection_returns_null_for_house() {
			
			DB myMockDatabase = mock(DB.class);
			Mockito.when(myMockDatabase.getCollection(Mockito.matches("house"))).thenReturn(null);
			RoomServiceMongoImpl myService = BuildTarget(myMockDatabase);
			
			assertEquals(0, myService.getAllRooms().size());
		}
	}
}

This allowed me to create the room service and inject into it the DB object, since as far as I can tell, I gain nothing by injecting the Mongo object. So, my first test is that we get back a null object in the form of an empty list when the MongoDB collection requested is empty. This makes sense, as it means we have no house (and thus no rooms).

When doing TDD, I’ve gotten in the habit of teasing out any control flow by varying up the return value of the method. So, the first test returns an empty collection. The next test will return count of one. The next will return a bunch. The one after that may go back to zero but in a different set of circumstances. This sort of progression allows me to make progress while covering all my bases. So, here, I’m going to do the setup necessary for a list with a single item to be returned.

But… as it turns out, this isn’t trivial. From my database, I’m getting a collection, which I’m asking for a database cursor, which I’m asking in a loop for an object called “next”, which I’m then asking for my actual room’s string value (its name). This has the Law-of-Demeter violating form db.getCollection().find().next().get(string). Ugh. That’s a lot of mocking, and creating mocks this way is the smell of LOD violations in your code. But, let’s suck it up for now and create the mocks:

@Test
public void returns_list_with_one_item_when_db_returns_matching_cursor() {

	DBObject myMockDatabaseObject = mock(DBObject.class);
	Mockito.when(myMockDatabaseObject.get(Mockito.anyString())).thenReturn("asdf");
	
	DBCursor myMockCursor = mock(DBCursor.class);
	Mockito.when(myMockCursor.next()).thenReturn(myMockDatabaseObject);
			
	DBCollection myMockCollection = PowerMockito.mock(DBCollection.class);
	Mockito.when(myMockCollection.find()).thenReturn(myMockCursor);
	
	DB myMockDatabase = mock(DB.class);
	Mockito.when(myMockDatabase.getCollection(Mockito.matches("house"))).thenReturn(myMockCollection);
	
	RoomServiceMongoImpl myService = BuildTarget(myMockDatabase);
	
	assertEquals(1, myService.getAllRooms().size());
}

This was particularly annoying to write since it cost me a good bit of frustration trying to figure out why I was getting cryptic error messages about the wrong return type. Turns out it was because the collection.find() method is final, requiring me to use PowerMockito (this stack overflow post ended my suffering and I voted the poster up both for the question and his self-answer as my way of saying thanks). But, I was done, and the following class made the test pass:

public class RoomServiceMongoImpl implements RoomService {

	/**
	 * This is the MongoDB database object we'll use for our queries
	 */
	private DB _database;
	
	/**
	 * Dependency injected constructor
	 * @param database
	 */
	public RoomServiceMongoImpl(DB database) {
		_database = database;
	}

	@Override
	public Collection getAllRooms() {
		Collection myRooms = new ArrayList();
		
		if(_database.getCollection("house") != null && _database.getCollection("house").find().next().get("bkag") != null) {
			myRooms.add(new RoomModel("blah", null, 'A'));
		}
		return myRooms;
	}
}

Hideous, with all of those LOD violations, but functional. So now, the next step is to factor the code toward something less obtuse with my TDD, and an excellent way to do this is the case of two strings in the database. But, in order to make that happen, the cursor mock’s next() method has to first return a DB object and then return null. It just so happens that this is goal number one, and it can be achieved as follows:

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);

Note the chained returns calls. I took what I had in the previous test and turned it into this code, and then amended the expected size of the collection returned to be 2 using hasNext() and next(). I also had to go back and add similar code to the previous test for completeness sake. With this in place, the CUT getAllRooms() method became:

@Override
public Collection getAllRooms() {
	Collection myRooms = new ArrayList();
	DBCollection myCollection = _database.getCollection("house");
	if(myCollection != null) {
		DBCursor myCursor = myCollection.find();
		if(myCursor != null) {
			while(myCursor.hasNext()) {
				myRooms.add(new RoomModel(myCursor.next().get("asdf").toString(), null, 'A'));
			}
		}
	}
	return myRooms;
}

Man, that’s ugly. But, it works, and that’s what matters. We’re going to pretty this up and make it more robust later during the “refactor” cycles of red-green-refactor. So, on to goal number two, which is to compact the mock setup code somewhat. Granted, the pain of this setup indicates a design smell and there is a good argument that we should not deoderize these test smells. But, given that this is partially an endeavor for me to learn and blog about the mocking tools available with JUnit, I’m going to make an exception, with a promise to myself that I will pay off this loan of technical debt later by doing what I can to cut down on the database boilerplate I don’t need in this class. I came up with this:

@Test
public void returns_list_with_one_item_when_db_returns_matching_cursor() {

	DBObject myMockDatabaseObject = mock(DBObject.class);
	Mockito.when(myMockDatabaseObject.get(Mockito.anyString())).thenReturn("asdf");
			
	DB myMockDatabase = mock(DB.class);
	
	Mockito.when(myMockDatabase.getCollection(Mockito.matches("house"))).thenReturn(PowerMockito.mock(DBCollection.class));
	Mockito.when(myMockDatabase.getCollection(Mockito.matches("house")).find()).thenReturn(mock(DBCursor.class));
	Mockito.when(myMockDatabase.getCollection(Mockito.matches("house")).find().next()).thenReturn(myMockDatabaseObject).thenReturn(null);
	Mockito.when(myMockDatabase.getCollection(Mockito.matches("house")).find().hasNext()).thenReturn(true).thenReturn(false);
	
	RoomServiceMongoImpl myService = BuildTarget(myMockDatabase);
	
	assertEquals(1, myService.getAllRooms().size());
}

But, it throws a null reference exception on the first find() setup (second Mockito.when() line) and it doesn’t really save any code anyway, so I see no advantage to going this route. My instincts were against it anyway, and since it doesn’t work as fluidly as I hoped it might (I feel like I could probably get this fluent-style to work with enough persistence) and doesn’t save us code, forget it. Nothing ventured, nothing gained. I’ll have to be satisfied that this fluent-chaining appears possible, but might be a stone better left un-turned in favor of going back and cleaning up my code by eliminating LOD violations and generally seeking not to have them.

These posts are getting a little fewer and further between now as I’m less frequently blazing new trails in my work. I will probably check back in on this line of posts when I factor the design of this a bit, as kind of a MongoDB/JUnit fusion post.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
trackback

[…] var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(po, s); })(); Last time in this series, I pulled an “Empire Strikes Back” and ended on a bit of a down note. […]