I'm creating a small java service that returns a list of restaurants depending on the selected place.
Data is retrieved from Riak using com.basho.riak:riak-client:2.0.0 and the read operation is wrapped in a TenacityCommand.
Important classes are described below and I would be happy if you could assist me in creating a solid and simple unit test.
Commands are created using a factory:
package service.command.factory;
import com.basho.riak.client.api.RiakClient;
import com.basho.riak.client.api.commands.kv.FetchValue;
import com.basho.riak.client.core.query.Location;
import com.basho.riak.client.core.query.Namespace;
import domain.Place;
import service.command.FetchRestaurantsCommand;
public class FetchRestaurantsCommandFactory {
private final RiakClient riakClient;
private final Namespace namespace;
public FetchRestaurantsCommandFactory(final RiakClient riakClient, final Namespace namespace) {
this.riakClient = riakClient;
this.namespace = namespace;
}
public FetchRestaurantsCommand create(final Place place) {
Location location = new Location(namespace, place.getName());
FetchValue riakCommand = new FetchValue.Builder(location).build();
return new FetchRestaurantsCommand(riakClient, riakCommand);
}
}
And the command looks like this:
package service.command;
import java.util.Optional;
import service.command.keys.WhereToEatDependencyKeys;
import com.basho.riak.client.api.RiakClient;
import com.basho.riak.client.api.commands.kv.FetchValue;
import com.basho.riak.client.api.commands.kv.FetchValue.Response;
import com.yammer.tenacity.core.TenacityCommand;
import domain.Restaurant;
import domain.RestaurantList;
public class FetchRestaurantsCommand extends TenacityCommand<Optional<RestaurantList>>{
private final RiakClient riakClient;
private final FetchValue fetchValue;
public FetchRestaurantsCommand(RiakClient riakClient, FetchValue fetchValue) {
super(WhereToEatDependencyKeys.RIAK_GET_RESTAURANTS);
this.fetchValue = fetchValue;
this.riakClient = riakClient;
}
#Override
protected Optional<RestaurantList> run() throws Exception {
Response response = riakClient.execute(fetchValue);
return Optional.ofNullable(response.getValue(RestaurantList.class));
}
#Override
protected Optional<RestaurantList> getFallback() {
return Optional.of(RestaurantList.createFallback(new Restaurant("My failure suggestion")));
}
}
The above classes are used like:
Place place = // Created from url parameter
RiakClient riakClient = // created on start using the app's conf
Namespace namespace = // created on start using the app's conf
FetchRestaurantsCommandFactory factory = new FetchRestaurantsCommandFactory(riakClient, namespace);
FetchRestaurantsCommand command = factory.create(place);
return command.execute();
Apart from the features provided by TenacityCommand, how should I assert that my system fetches data as expeceted?
My initial idea was to mock a RiakClient to return a predefined FetchValue.Response and then make assertions on the resulting RestaurantList.
Unfortunately its not possible to instantiate or Mockito.mock a FetchValue.Response due to its design.
The accepted answer in How to mock riak java client? describes why Mockito won't work.
As far a I understood you want to write unit test. So you want to test that assuming some Response whether Optional<RestaurantList> instance is constructed correctly or not.
What I can think of is to wrap riakClient.execute(fetchValue); in a protected (or package private) helper function like:
Response fetch() {
return riakClient.execute(fetchValue);
}
Then in your test you can inherit from FetchRestaurantsCommand and override fetch function by returning any Response
Now, you can write any test to see whether the conversion of given Response to Optional<RestaurantList> behaves as expected or not.
If you need entire code and my explanation is not clear enough let me know to provide it.
I ended up using PowerMock as suggested by #gontard. See my unit test on GitHub: FetchRestaurantsCommandTest.java
I considered to create a fake/mock RiakClient in the com.basho.riak.client package. Such class could hopefully instantiate the Response object in the same way as the real client does. It would probably work for fetchValue but it would grow too big when involving more advanced Riak concepts s.a. siblings.
Related
Given a Spring Data REST (SDR) server built with Spring Boot Gradle Plugin 2.2.5.RELEASE, is it possible to load an #Entity by self link within the server application?
I'm aware how to access it with an HTTP client, e.g. using curl:
$ curl localhost/users/1 # Responds with 200 OK and JSON representation
What I'm searching for is a mechanism to do this in the server using Java only, ideally using a standard SDR mechanism:
#Service
public class SelfLinkResolver {
public Object findBySelfLink(Link self) {
if (self == null || !self.getRel().equals(SELF)) {
throw new IllegalArgumentException("Non-null self link expected");
}
return null; // How to return the entity using a standard SDR mechanism?
}
public void exampleCall() {
Link self = new Link("localhost/users/1");
Object entity = findBySelfLink(self);
requireNonNull(entity, "Failed to load entity by self link");
}
}
An internal solution is parse your link and extract the ID (1 in your example), the call repository.findById(id).
Another solution would be new a RestTemplate, call your own API.
I finally came up with this solution, which uses SDR's UriToEntityConverter. In contrast to my question, it requires not only the self link, but also the entity class. It therefore doesn't fully answer my initial question.
I guess that there is no SDR solution that does not require the entity class, since there is no need for this within the framework, at least for usual API calls. SDR is always provided with the type information through the Repository, to which the self link refers. However, I didn't dive into other classes such as PersistentEntities, RepositoryInvokerFactory or Repositories, which might provide a solution for this.
WARNING: My tested implementation differs from this. This code is untested, but should illustrate the idea.
import lombok.NonNull;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.repository.support.RepositoryInvokerFactory;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.hateoas.Link;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import static org.springframework.hateoas.IanaLinkRelations.SELF;
#Component
public class SelfLinkToEntityConverter extends UriToEntityConverter {
private static final TypeDescriptor URI_DESCRIPTOR = TypeDescriptor.valueOf(URI.class);
SelfLinkToEntityConverter(#NonNull PersistentEntities entities,
#NonNull RepositoryInvokerFactory invokerFactory,
#NonNull Repositories repositories) {
super(entities, invokerFactory, repositories);
}
#NonNull
public <T> Optional<T> findBySelfLink(#NonNull Link self, #NonNull Class<T> entityClass) {
checkArgument(self.getRel().equals(SELF), "Non-null self link expected");
URI uri = self.expand().toUri();
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(entityClass);
try {
#SuppressWarnings("unchecked")
T entity = (T) super.convert(uri, URI_DESCRIPTOR, typeDescriptor);
return Optional.ofNullable(entity);
} catch (IllegalArgumentException o_O) {
throw new IllegalArgumentException(format("Failed to load %s: %s",
entityClass.getSimpleName(), self.getHref()));
}
}
}
I have written some unit tests for a static method. The static method takes only one argument. The argument's type is a final class. In terms of code:
public class Utility {
public static Optional<String> getName(Customer customer) {
// method's body.
}
}
public final class Customer {
// class definition
}
So for the Utility class I have created a test class UtilityTests in which I have written tests for this method, getName. The unit testing framework is TestNG and the mocking library that is used is Mockito. So a typical test has the following structure:
public class UtilityTests {
#Test
public void getNameTest() {
// Arrange
Customer customerMock = Mockito.mock(Customer.class);
Mockito.when(...).thenReturn(...);
// Act
Optional<String> name = Utility.getName(customerMock);
// Assert
Assert.assertTrue(...);
}
}
What is the problem ?
Whereas the tests run successfully locally, inside IntelliJ, they fail on Jenkins (when I push my code in the remote branch, a build is triggered and unit tests run at the end). The error message is sth like the following:
org.mockito.exceptions.base.MockitoException: Cannot mock/spy class
com.packagename.Customer Mockito
cannot mock/spy because :
- final class
What I tried ?
I searched a bit, in order to find a solution but I didn't make it. I note here that I am not allowed to change the fact that Customer is a final class. In addition to this, I would like if possible to not change it's design at all (e.g. creating an interface, that would hold the methods that I want to mock and state that the Customer class implements that interface, as correctly Jose pointed out in his comment). The thing that I tried is the second option mentioned at mockito-final. Despite the fact that this fixed the problem, it brake some other unit tests :(, that cannot be fixed in none apparent way.
Questions
So here are the two questions I have:
How that is possible in the first place ? Shouldn't the test fail both locally and in Jenkins ?
How this can be fixed based in the constraints I mentioned above ?
Thanks in advance for any help.
An alternative approach would be to use the 'method to class' pattern.
Move the methods out of the customer class into another class/classes, say CustomerSomething eg/CustomerFinances (or whatever it's responsibility is).
Add a constructor to Customer.
Now you don't need to mock Customer, just the CustomerSomething class! You may not need to mock that either if it has no external dependencies.
Here's a good blog on the topic: https://simpleprogrammer.com/back-to-basics-mock-eliminating-patterns/
How that is possible in the first place? Shouldn't the test fail both locally and in Jenkins ?
It's obviously a kind of env-specifics. The only question is - how to determine the cause of difference.
I'd suggest you to check org.mockito.internal.util.MockUtil#typeMockabilityOf method and compare, what mockMaker is actually used in both environments and why.
If mockMaker is the same - compare loaded classes IDE-Client vs Jenkins-Client - do they have any difference on the time of test execution.
How this can be fixed based in the constraints I mentioned above?
The following code is written in assumption of OpenJDK 12 and Mockito 2.28.2, but I believe you can adjust it to any actually used version.
public class UtilityTest {
#Rule
public InlineMocksRule inlineMocksRule = new InlineMocksRule();
#Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
#Test
public void testFinalClass() {
// Given
String testName = "Ainz Ooal Gown";
Client client = Mockito.mock(Client.class);
Mockito.when(client.getName()).thenReturn(testName);
// When
String name = Utility.getName(client).orElseThrow();
// Then
assertEquals(testName, name);
}
static final class Client {
final String getName() {
return "text";
}
}
static final class Utility {
static Optional<String> getName(Client client) {
return Optional.ofNullable(client).map(Client::getName);
}
}
}
With a separate rule for inline mocks:
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.MockUtil;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class InlineMocksRule implements TestRule {
private static Field MOCK_MAKER_FIELD;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
VarHandle modifiers = lookup.findVarHandle(Field.class, "modifiers", int.class);
MOCK_MAKER_FIELD = MockUtil.class.getDeclaredField("mockMaker");
MOCK_MAKER_FIELD.setAccessible(true);
int mods = MOCK_MAKER_FIELD.getModifiers();
if (Modifier.isFinal(mods)) {
modifiers.set(MOCK_MAKER_FIELD, mods & ~Modifier.FINAL);
}
} catch (IllegalAccessException | NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
}
#Override
public Statement apply(Statement base, Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
Object oldMaker = MOCK_MAKER_FIELD.get(null);
MOCK_MAKER_FIELD.set(null, Plugins.getPlugins().getInlineMockMaker());
try {
base.evaluate();
} finally {
MOCK_MAKER_FIELD.set(null, oldMaker);
}
}
};
}
}
Make sure you run the test with the same arguments. Check if your intellij run configurations match the jenkins. https://www.jetbrains.com/help/idea/creating-and-editing-run-debug-configurations.html. You can try to run test on local machine with the same arguments as on jenkins(from terminal), if it will fail that means the problem is in arguments
I've recently been tasked with benchmarking some features of our API, and I've been using Caliper to do so. It seems fairly straightforward and is actually quite powerful, I've been following the tutorials here:
How to use caliper
Tutorial from the creator
I'm working with Guice in our current app, so when I try to run the benchmark, I make sure to inject the services I need
Provided below is my code. I've tried setting the variables with their own #injected annotation, I've tried initiating them directly (although there's too many dependencies to deal with, and I'd have to initiate them as well). #Parms annotation won't work because I need to have a string type to iterate through (the parms only takes strings, there's documentation of what to do if it's another type, but it needs a .toString type method)
package com.~~~.~~~.api.benchmark;
import com.~~~.ds.mongo.orm.user.User;
import com.~~~.~~~.api.exceptions.auth.AccessDeniedException;
import com.~~~.~~~.api.service.authorization.UserService;
import com.~~~.~~~.api.service.campaign.CampaignMetadataService;
import com.google.caliper.BeforeExperiment;
import com.google.caliper.Benchmark;
import com.google.caliper.Param;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class CampaignBenchmark {
// === SERVICE INJECTIONS
#Inject
private CampaignMetadataService campaignMetadataService;
#Inject
private UserService userService;
// =============================================
// === BENCHMARK PARMS
#Param({
"7ca8c319",
"49191829"
})
String userId;
#Param({
"485",
"500"
})
String hashAdvertiserId;
// =============================================
// === TESTING PARMS
private User user;
// =============================================
// === SETUP
#Inject
public CampaignBenchmark(){
Injector injector = Guice.createInjector();
this.userService = injector.getInstance(UserService.class);
this.campaignMetadataService = injector.getInstance(CampaignMetadataService.class);
}
#BeforeExperiment
void setUp(){
this.user = userService.getUserByHashedId(userId);
}
// =============================================
// === BENCHMARKS
#Benchmark
int fetchAllCampaign(int reps) throws AccessDeniedException {
VideoIqUser user = this.user;
String hashAdvertiserId = this.hashAdvertiserId;
int dummy = 0;
for(int i=0 ; i<reps ; i++){
dummy |= campaignMetadataService.fetchAllCampaigns(user, hashAdvertiserId).size();
}
return dummy;
}
}
When we try to run it with
mvn exec:java -Dexec.mainClass="com.google.caliper.runner.CaliperMain" -Dexec.args="com.~~~.~~~.api.benchmark.CampaignBenchmark"
We get the following
WARNING: All illegal access operations will be denied in a future release
Experiment selection:
Benchmark Methods: [fetchAllCampaign]
Instruments: [allocation, runtime]
User parameters: {hashAdvertiserId=[485, 500], userId=[7ca8c319, 49191829]}
Virtual machines: [default]
Selection type: Full cartesian product
This selection yields 16 experiments.
Could not create an instance of the benchmark class following reasons:
1) Explicit bindings are required and com.~~~.~~~.api.service.campaign.CampaignMetadataService is not explicitly bound.
2) Explicit bindings are required and com.~~~.~~~.api.service.authorization.UserService is not explicitly bound.
The question is: at what point should I be doing injections, and how do I go about that? Should I have a wrapper class setup?
Quick Update
I forgot to mention that is part of a DropWizard (0.7.1) application. We use resources and inject them into the environment
Ex:
environment.jersey().register(injector.getInstance(CampaignManagementResource.class));
These resources contain the services needed to run them, and they are included as #Inject, though we never actually specify a binding anywhere else.
#Inject
private CampaignMetadataService apiCampaignMetadataService;
Is there something I should adjust for DropWizard, or should I just mock up the services?
Think of Guice as nothing but a Hashtable. Working with Guice has these parts:
creating the hashtable
putting things into it
getting things out of it
Your code creates the hashtable and queries it, but never puts anything in it:
public CampaignBenchmark() {
// Creating the hashtable
Injector injector = Guice.createInjector();
// Retrieving from the hashtable
this.userService = injector.getInstance(UserService.class);
this.campaignMetadataService = injector.getInstance(CampaignMetadataService.class);
}
The Guice hashtable is populated in Module classes. Undoubtedly, you have a CampaignModule or AdvertisingModule lying around somewhere. It probably looks like this:
public class CampaignModule extends AbstractModule {
#Override protected void configure() {
bind(CampaignMetadataService.class).to(CampaignMetadataServiceImplementation.class);
}
What that does is put a <CampaignMetadataService, CampaignMetadataServiceImplementation> entry into the Guice hashtable. Going forward, whoever asks for an instance of CampaignMetadataService receives an instance of CampaignMetadataServiceImplementation.
So in your code, you need to let Guice know about this module:
public CampaignBenchmark() {
// Creating the hashtable and letting modules populate it
Injector injector = Guice.createInjector(new CampaignModule(), new UserModule());
// Retrieving from the hashtable
this.userService = injector.getInstance(UserService.class);
this.campaignMetadataService = injector.getInstance(CampaignMetadataService.class);
}
Everything else you do is fine. Side note: #Inject annotations on your constructor and fields don't do anything, since you never ask Guice to supply you with instances of CampaignBenchmark. These can just be deleted.
According to https://cloud.google.com/appengine/docs/java/tools/localunittesting#Writing_HRD_Datastore_Tests, "If your app uses the High Replication Datastore (HRD), you may want to write tests that verify your application's behavior in the face of eventual consistency. LocalDatastoreServiceTestConfig exposes options that make this easy." You're supposed to set setDefaultHighRepJobPolicyUnappliedJobPercentage(100) and then, "By setting the unapplied job percentage to 100, we are instructing the local datastore to operate with the maximum amount of eventual consistency. Maximum eventual consistency means writes will commit but always fail to apply, so global (non-ancestor) queries will consistently fail to see changes."
However, I don't think setDefaultHighRepJobPolicyUnappliedJobPercentage(100) works.
If it did, then my test case below, testEventualConsistency() should pass but it it fails on the second assertion. On the first assertion, I read back an object I've saved using an Objectify ancestor() query. It works as documented because the object is retrieved. However, the second assertion fails. In that assertion I've also read back the object I've saved but I haven't used an Objectify ancestor() query so it shouldn't retrieve anything because I've specified that no jobs should complete (i.e. the setDefaultHighRepJobPolicyUnappliedJobPercentage(100) setting).
EventualConsistencyTest Test Case
import static com.googlecode.objectify.ObjectifyService.begin;
import static com.googlecode.objectify.ObjectifyService.ofy;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import java.util.List;
import org.junit.Test;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.util.Closeable;
import com.netbase.followerdownloader.model.DownloadTask;
import com.netbase.followerdownloader.model.User;
public class EventualConsistencyTest {
private final LocalServiceTestHelper helper =
new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
.setDefaultHighRepJobPolicyUnappliedJobPercentage(100));
#Test
public void testEventualConsistency() {
helper.setUp();
ObjectifyRegistrar.registerDataModel();
User user = new User();
user.id = 1L;
Closeable closeable1 = begin();
ofy().save().entity(user);
closeable1.close();
Closeable closeable2 = begin();
DownloadTask downloadTask = new DownloadTask();
downloadTask.owner = Ref.create(user);
ofy().save().entity(downloadTask);
closeable2.close();
Closeable closeable3 = ObjectifyService.begin();
List<DownloadTask> downloadTasks1 = ofy().load().type(DownloadTask.class).ancestor(user).list();
assertThat(downloadTasks1.size(), equalTo(1));
closeable3.close();
Closeable closeable4 = ObjectifyService.begin();
List<DownloadTask> downloadTasks2 = ofy().load().type(DownloadTask.class).list();
assertThat(downloadTasks2.size(), equalTo(0)); // THIS SHOULD PASS IF setDefaultHighRepJobPolicyUnappliedJobPercentage(100) WORKED
closeable4.close();
helper.tearDown();
}
}
User Definition
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
#Entity
public class User {
#Id public Long id;
public User () {
}
}
DownloadTask Definition
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
#Entity
public class DownloadTask {
#Id public Long id;
#Parent public Ref<User> owner;
public DownloadTask() {
}
}
Environment:
appengine-api-1.0-sdk-1.9.17.jar
appengine-testing-1.9.17.jar
appengine-api-stubs-1.9.17.jar
junit-4.11.jar
objectify-5.1.3.jar
In case I missed anything else important, here is a more exhaustive list:
My questions are:
Is setDefaultHighRepJobPolicyUnappliedJobPercentage(100) broken?
Does setDefaultHighRepJobPolicyUnappliedJobPercentage(100) not really work as documented? Does it in fact apply the job even though the documentation says it's not supposed to?
Is the value passed to setDefaultHighRepJobPolicyUnappliedJobPercentage() really supposed to be 100 and not maybe let's say, 1.0f?
Do Objectify ancestor queries not really work as documented?
The problem is explained by an observation at https://cloud.google.com/appengine/docs/java/tools/localunittesting#Java_Writing_High_Replication_Datastore_tests :
"In the local environment, performing a get() of an Entity that belongs to an entity group with an unapplied write will always make the results of the unapplied write visible to subsequent global queries."
In this contect, this means the ancestor-query:
List<DownloadTask> downloadTasks1 = ofy().load().type(DownloadTask.class).ancestor(user).list();
which internally "performs a get() of an Entity that belongs to an entity group with an unapplied write" influences the behavior of the immediately-following global query:
List<DownloadTask> downloadTasks2 = ofy().load().type(DownloadTask.class).list();
To avoid your tests influencing each other, and in particular, interfering w/each other in this way, it's best to use a separate method per operation under test (each with all the needed setup and teardown parts), rather than having successive operations-under-test within a single test method.
A lot of the functionality in guava is provided by static methods. I haven't figured out how to merge the use of guava libraries and good Dependency Injection practice.
For example, if I were to use
Files.readLines(File, Charset)
then I find I have a hard time writing a unit test which doesn't touch the filesystem, which I only like to do for integration testing.
I guess it's possible that I could write an adapter for all of the ones I'm interested in? But that could possibly end up being a lot of work...
I find it odd that the guava libraries come from the same set of people that provide guice and write blog posts like this
Ugh, the dreaded static methods. I have heard that JMockit is capable of mocking out statics, but I've never tried it myself. The solution I typically use is an Adapter.
public class FilesAdapter {
private final File file;
public FilesAdapter( File file ) {
this.file = file;
}
public List<String> readLines( Charset charset ) {
return Files.readLines( file, charset );
}
}
You can optionally have FilesAdapter implements an interface, although since this is a single purpose object, I typically wouldn't.
GUICE is capable of injecting concrete objects, and mocking frameworks such as JMock2 & Mockito are able to mock concretes as well. This is all a matter of academics and different people will have different opinions.
If you were using GUICE, you would wrap this guy in a factory for injection goodness.
public class FilesAdapter {
private final File file;
#Inject
protected FilesAdapter( #Assisted File file ) {
this.file = file;
}
public List<String> readLines( Charset charset ) {
return Files.readLines( file, charset );
}
public interface Factory {
FilesAdapter create( File file );
}
}
We provided the common.io library as a stopgap until you finally get a real, proper filesystems API in JDK 7. That library will be interface-based and very testing-friendly.
Powermock allows you to mock static methods.
We're using it here and there, and I can testify that it works.
You can isolate the static dependency into it's own method (which can then be over-ridden for test purposes, as shown below). Usually, the isolated method would have 'protected' visibility.
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import java.io.File;
import org.junit.Before;
import org.junit.Test;
public class FileUtilsTest {
private static final String[] TEST_LINES = { "test 1", "test 2", "test 3" };
private static final File TEST_FILE = new File("/foo/bar");
private FileUtils fileUtils;
/**
* Configure reusable test components
*/
#Before
public void setUp() {
fileUtils = spy(new FileUtils());
when(fileUtils.getLines(any(File.class))).thenReturn(TEST_LINES);
}
/**
* Ensure the {#link FileUtils#countLines(File)} method returns the correct line count (without hitting the
* file-system)
*/
#Test
public void testCountLines() {
assertEquals(3, fileUtils.countLines(TEST_FILE));
}
/**
* The class we want to test
*/
public static class FileUtils {
/**
* Method that we want to test
*/
public int countLines(final File file) {
return getLines(file).length;
}
/**
* Static dependency isolated to a method (that we can override, for test purposes)
*/
public String[] getLines(final File file) {
return Files.readLines(file);
}
}
/**
* Poorly written utility class with hard-to-test static methods
*/
public static class Files {
public static String[] readLines(final File file) {
// In reality, this would hit the file-system
return new String[] { "line 1" };
}
}
}
First of all, why not just let your unit tests touch the file system? Usually, reading small local test-specific files in a few tests adds no perceptible delay to the test run.
But if you really don't want to touch the file system, then simply mock it. For example, the following JMockit-based test should work:
#Test
public void someTest()
{
new Expectations() {
#Mocked Files files;
#Input List<String> lines = asList("Line 1", "Another line", "...");
};
// Called from code under test:
List<String> lines = Files.readLines(someFile, charSet);
// These "lines" will be the same as the "#Input" lines.
// asserts...
}
Provide your own stubs for the static methods you call and let the classloader 'inject' them for you.
In general, it's not really a problem to solve with DI and mocks. Some dependencies just aren't worth explicitly injecting if they're common enough to be assumed to be dependencies and you're confident enough that they work as advertised. I think Guava falls into this category. Likewise, mocks are great at eliding state manipulations, which static methods shouldn't be mucking around with anyway, so you're not going to gain much even if you manage to mock them out.