Asad Iqbal
Asad Iqbal

Reputation: 3321

How should I unit-test a long function?

If i have a long method of code which gathers data from 2 or 3 difference sources and returns a result. How can I refactor it so that it is more unit-testable? This method is a webservice and I want to make one call from client code to gather all the data.

I can refactor some portions out into smaller methods which will be more testable. But the current method will still be calling those 5 methods and will remain less testable. Assuming Java as programming language, is there a pattern for making such code testable?

Upvotes: 6

Views: 3202

Answers (2)

robjohncox
robjohncox

Reputation: 3665

This is a very common testing problem, and the most common solution I come across for this is to separate the sourcing of data from the code which uses the data using dependency injection. This not only supports good testing, but is generally a good strategy when working with external data sources (good segregation of responsibilities, isolates the integration point, promotes code reuse being some reasons for this).

The changes you need to make go something like:

  • For each data source, create an interface to define how data from that source is accessed, and then factor out the code which returns the data into a separate class which implements this.
  • Dependency inject the data source into the class containing your 'long' function.
  • For unit testing, inject a mock implementation of each data source.

Here is some code examples showing what this would look like - note that this code is merely illustrative of the pattern, you will need some more sensible names for things. It would be worth studying this pattern and learning more about dependency injection & mocking - two of the most powerful weapons in the unit testers armory.

Data Sources

public interface DataSourceOne {
    public Data getData();
}

public class DataSourceOneImpl implements DataSourceOne {
    public Data getData() {
        ...
        return data;
    }
}

public interface DataSourceTwo {
    public Data getData();
}

public class DataSourceTwoImpl implements DataSourceTwo {
    public Data getData() {
        ...
        return data;
    }
}

Class with Long Method

public class ClassWithLongMethod {
    private DataSourceOne dataSourceOne;
    private DataSourceTwo dataSourceTwo;

    public ClassWithLongMethod(DataSourceOne dataSourceOne,
                               DataSourceTwo dataSourceTwo) {
        this.dataSourceOne = dataSourceOne;
        this.dataSourceTwo = dataSourceTwo;
    }

    public Result longMethod() {
        someData = dataSourceOne.getData();
        someMoreData = dataSourceTwo.getData();
        ...
        return result;
    }
}

Unit Test

import org.junit.Test;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ClassWithLongMethodTest {

    @Test
    public void testLongMethod() {

        // Create mocked data sources which return the data required by your test
        DataSourceOne dataSourceOne = mock(DataSourceOne.class);
        when(dataSourceOne.getData()).thenReturn(...);
        DataSourceTwo dataSourceTwo = mock(DataSourceTwo.class);
        when(dataSourceTwo.getData()).thenReturn(...);

        // Create the object under test using the mocked data sources
        ClassWithLongMethod sut = new ClassWithLongMethod(dataSourceOne,
                                                          dataSourceTwo);

        // Now you can unit test the long method in isolation from it's dependencies
        Result result = sut.longMethod();

        // Assertions on result
        ...
    }
}

Please forgive (and correct) any syntactic mistakes, I don't write much java these days.

Upvotes: 5

Dave Newton
Dave Newton

Reputation: 160191

The test for the "big" method will look like integration test where the smaller methods can be mocked.

If you can separate the "big" method into five isolated methods, then the "big" method could be further partitioned into semantically-/contextually-meaningful groups of the isolated methods.

Then you can mock the larger groupings of isolated methods for the "big" method.

Upvotes: 0

Related Questions