Part 4 – Adding our unit tests and the Mockito framework

I hope you’ve been asking yourself, “Hey Steve!  Where’s the unit tests?  I thought you were a big Test Driven Development guy.”  Yup, I violated our code of ethics, committed an awful crime, and generally was a bad example … sort of.  Actually I was using some unit tests that I didn’t publish.  But since this is growing into a real library, it’s time to use best practices.

The Trivial Unit Test

Our shouldVisit method is a good trivial method to lock down.  It’s so trivial you might wonder why it needs a test.  Answer: testing is to prevent future bugs from creeping in.  If there is a behavior change in the system, you want the flag raised.  The shouldVisit function is trivial, but the impact on the system is huge.  Any change in here will affect our results.  (Besides, we are going to refactor this soon, so we need a ratchet in place to protect us.)

And the first test

This doesn’t even compile.  So we need to make shouldVisit public.  Hmmm, this is smelling bad already.  You don’t willy-nilly make functions public just to get your test to compile.  But, for purpose of explanation, let’s just make it public.  Let’s see where we end up.

The test now compiles, but fails instantly with a nullPointer exception at line 8 in shouldVisit().  Why?  Because we never called go() which sets urlBase.  Now what?  There are several options:

  1. Throwout the idea of unit tests (yes, I have had engineers press me for this!)
  2. Make urlBase public or provide a setter (logically the same thing).  Yuck.  You do not want to sacrifice good class design principles for unit testing.
  3. Move the starting URL to the constructor.  Not a bad idea, but we want to keep the flexibility of changing the URL in the go() function.
  4. Add a null test to the method.  Nope.  We still need to test the urlBase functionality to run the test.
  5. Using reflection we can force in a urlBase.  That smells worse than number 2.
  6. Back up and reassess.

Let’s take option 6 here.  We want to test the behavior of a class right?  The implementation of shouldVisit() is private and owned by the class.  So, maybe our testing granularity is a little too detailed.  So, we’ll scrub that idea and work at a higher level of abstraction.

Enter the Mockito library

What we need to do is ‘mock-out’ our the classes used in GrabManager so we can control things a little better.  Then we can test the results of our class’s processing.

First add a dependency to mockito in the pom.xml. We are going to use the latest (at the time of this article) to get full Java 8 support.

Next we re-write our test with the appropriate mocks:

Let’s review some of the key lines:

  • Line 26 – We mock-out the Java ExecutorService.  We don’t actually need background processing for our test.  That’s a specialized sort of testing anyway … maybe later.
  • Line 27 – Create our class under test: GrabManager.  The @InjectMocks annotation tells Mochito to look for objects to replace inside our grabManager instance.  Yeah, magic again.  Just go with it.
  • Lines 29-32 – Tell Mockito to pull a rabbit out of his hat.
  • Lines 36-43 – Create a set of urls we are going to pretend existing on our pretend crawled page.
  • Lines 46-47 – Create a mock GrabPage object.  This will return our pretend set of urls on demand.
  • Lines 50-52 – Because our GrabManager class is dealing with Future objects, we need to fake it out.  We’ll just say the thread is always done (line 51) and we have a GrabPage object ready to go (line 52).
  • Line 55 – Let the GrabManager actually think it has submitted a task to the ExecutorService, but really it’s just our mocked future object.
  • Line 57 – Finally, we are going to run the code.
  • Lines 59-64 – Validate the results are as expected.

By now you are probably winded reading thru that entire test.  Frankly, the test is more complicated and longer than the code it is testing.  You might think that’s a problem.  Actually it’s a good sign.

Why?  Because clean code is normally very short and precise.  It will cover a range of behaviors and conditions.  So, your tests need to exercise a range of behaviors and conditions.  That just requires a bunch of code and clever tricks.  It’s ok to have more testing code that actual code.