Reputation: 3321
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
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:
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
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