Question

An example of scenario I'm trying to unit test somehow:

A method gets three parameters: Company name, Plane Number 1, Plane Number 2

public Double getDistanceBetweenPlanes(Sting company, String plane1, String plane2)
{
  -Apply some validation logic to see that plane numbers and company realy exists
  -Use complex geLocation and setelate data to get plane location

  return plane1.geoLocation() - plane2.geoLocation() ;
}

Now, I want to unit test this.

I do not have real plane numbers, I do not have live connection to the satellite.

Still I would like to be able to mock the whole thing somehow so that I can test this API. Any suggestion on how to refactor it? Any new public method to be created just for testing?

Was it helpful?

Solution

Usually when refactoring non-tested code, I'm referring to what I would do when practicing TDD and BDD.

So first I would write a simple contract, one line = one requirement (of the existing code). Then while coding the test, I would probably notice things I don't want to test. In this case, DI will help you remove the code that has different responsibility to another class. When working in a object design it is important to focus on behavior and interaction between objects with different concerns. Mockito, with is alias class BDDMockito, can help you favor this approach.

In your case, here's an example of what you can do.

public class PlaneLocator {
    private CompanyProvider companyProvider;
    private PlaneProvider companyProvider;
    private LocationUtil locationUtil;

    // Constructor injection or property injection

    public Double getDistanceBetweenPlanes(String companyId, String plane1Id, String plane2Id) {
        Company company = companyProvider.get(company);
        Plane plane1 = planeProvider.get(plane1Id);
        Plane plane1 = planeProvider.get(plane1Id);

        return locationUtil.distance(plane1.geoLocation(), plane2.geoLocation());
    }
}

A simple JUnit test might look like (using features from mockito 1.9.0) :

@RunWith(MockitoJUnitRunner.class)
public class PlaneLocatorTest {
    @Mock CompanyProvider mockedCompanyProvider;
    @Mock PlaneProvider mockedPlaneProvider;
    @Mock LocationUtil locationUtil;
    @InjectMocks SatelitePlaneLocator testedPlaneLocator;

    @Test public void should_use_LocationUtil_to_get_distance_between_plane_location() {
        // given
        given(companyProvider.get(anyString())).willReturn(new Company());
        given(planeProvider.get(anyString()))
                .willReturn(Plane.builder().withLocation(new Location("A")))
                .willReturn(Plane.builder().withLocation(new Location("B")));

        // when
        testedPlaneLocator.getDistanceBetweenPlanes("AF", "1251", "721");

        // then
        verify(locationUtil).distance(isA(Location.class), isA(Location.class));
    }

    // other more specific test on PlaneLocator
}

And you will have the following dependencies injected, each having his own unit test describing how the class behaves, considering the input and the collaborators :

public class DefaultCompanyProvider implements CompanyProvider {
    public Company get(String companyId) {
        companyIdValidator.validate(companyId);

        // retrieve / create the company
        return company;
    }
}
public class SatellitePlaneProvider implements PlaneProvider {
    public Plane get(Plane planeId) {
        planeIdValidator.validate(planeId);

        // retrieve / create the plane with costly satellite data (using a SatelliteService for example)
        return plane;
    }
}

Using this way you can refactor your code with a much lower coupling. The better separation of concerns will allow a better understanding of the code base, a better maintainability, and easier evolution.

I also would like to redirect you to further reading on this blog post The Transformation Priority Premise in TDD from the famous Uncle Bob Martin http://cleancoder.posterous.com/the-transformation-priority-premise

OTHER TIPS

You may use mocks like jmock or easy mock.

With jmock you'll write something like this:

@Test
public testMethod(){
 Mockery context = new Mockery();
 plane1 = context.mock(Plane.class);
 plane2 = context.mock(Plane.class);

 context.expectations(new Expectations(){{
   oneOf(plane1).geoLocation(); will(returnValue(integerNumber1));
   oneOf(plane2).geoLocation(); will(returnValue(integerNumber2));
 }});
 assertEquals(
   instanceUnderTest.method(plane1,plane2),
   integerNumber1-integerNumber2
 )
 context.assertIsSatisfied()
}  

The last method will assure the methods setted on expectations are called. If not an exception is raised and the test fails.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top