Update:
Some 10 years later perhaps the best way to test a private method, or any inaccessible member, is via @Jailbreak
from the Manifold framework.
@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;
This way your code remains type-safe and readable. No design compromises, no overexposing methods and fields for the sake of tests.
If you have somewhat of a legacy Java application, and you're not allowed to change the visibility of your methods, the best way to test private methods is to use reflection.
Internally we're using helpers to get/set private
and private static
variables as well as invoke private
and private static
methods. The following patterns will let you do pretty much anything related to the private methods and fields. Of course, you can't change private static final
variables through reflection.
Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);
And for fields:
Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
Notes:
1. TargetClass.getDeclaredMethod(methodName, argClasses)
lets you look into private
methods. The same thing applies for
getDeclaredField
.
2. The setAccessible(true)
is required to play around with privates.
Short answer. Change RecipeJDO.setIngredients()
to this:
public void setIngredients(List<IngredientJDO> ingredients) {
this.ingredients.clear();
this.ingredients.addAll(ingredients);
}
When you fetch the RecipeJDO, the ingredients
list is not a real ArrayList
, it is a dynamic proxy that handles the persistence of the contained elements. You shouldn't replace it.
While the persistence manager is open, you can iterate through the ingredients
list, add items or remove items, and the changes will be persisted when the persistence manager is closed (or the transaction is committed, if you are in a transaction). Here's how you would do the update without a transaction:
public void updateRecipe(String id, List<IngredientDTO> newIngredients) {
List<IngredientJDO> ingredients = convertIngredientDtosToJdos(newIngredients);
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
RecipeJDO recipe = pm.getObjectById(RecipeJDO.class, id);
recipe.setIngredients(ingredients);
} finally {
pm.close();
}
}
If you never modify the IngredientJDO
objects (only replace them and read them), you might want to make them Serializable
objects instead of JDO objects. If you do that, you may be able to reuse the Ingredient
class in your GWT RPC code.
Incidentally, even if Recipe
was not a JDO object, you would want to make a copy in the setIngredients()
method, otherwise someone could do this:
List<IngredientJDO> ingredients = new ArrayList<IngredientJDO>;
// add items to ingredients
recipe.setIngredients(ingredients);
ingredients.clear(); // Woops! Modifies Recipe!
Best Answer
Have you tried some of the methods mentioned here?