views:

181

answers:

5

Is there any way of replacing the logic within a private method when running a JUnit test?

A bit of background: we have some private methods which interact with bundles within an OSGi container. This is not available in the unit test therefore the methods will fail.

We have looked at JMockIt but the method replace functionality seems to want to force you to replace all the methods in the class which call one another.

The implementation would be something like this:

public final doSomething() {  
    firstThing();
    secondThing();
}
private firstThing() {  
    // normal code
}
private secondThing() {  
    // code which is unavailable in a unit test
}

And the unit test would specify the new implementation of secondThing():

// replace secondThing() in impl with this secondThing()

private secondThing() {  
    // dummy code
}

// run tests
+1  A: 

My advice - redesign your application. If you want to change the behaviour of a private method:

  • make it protected / public and override it in a mock-like object
  • move the functionality out of the method into a helper class, which is injectable (via dependency injection). Then mock that helper an inject the mock into the class-under-test, instead of the original heloper.

A workaround may be some byte-code manipulation technique, but I don't recommend such.

Bozho
So, not even `java.lang.reflect.Proxy` should be used? It does manipulate bytecode, you know...
Rogerio
no, it works 'by interface' only
Bozho
+2  A: 

You are coupling your implementation to the creation of the osgi object (doing it inside secondThing() or the class itself). If you passed the implementation into your class from the outside, you could use a stub/mock when testing instead.

krosenvold
I also think this is the right fix. In my opinion, any component which interacts with the outside world should be an object provided via a constructor/factory parameter (or otherwise specifiable) and not hardcoded into the class. (Obviously you have to have “leaves” to bottom out on which do inherently refer to the outside world (e.g. server names), but those you test differently.) Then any subsection of your program other than those leaves may be tested inside a completely controlled universe.
Kevin Reid
+2  A: 

I also think dependency injection would solve this problem. If you don't want another framework in your project and this is the only place which makes trouble, you could define an interface for secondThing and have 2 implementations for that, one for the original code and an empty one for the unittest.

stacker
A: 
manuel aldana
The `final` keyword is an important mechanism for OO design in Java.For JMockit it makes no difference, so use it as much as you want.
Rogerio
yes 'final' is a java language feature, which has its uses. but for me it is a anti-pattern if people are using it as a default modifier. i had often cases, where i had to get tests in place depending on legacy code. final was used and i was doomed for stubbing. in my cases mock-frameworks weren't the right tool (-> for certain reasons i had to build own hand-coded stubs).
manuel aldana
+2  A: 

You certainly can solve this situation with JMockit. One way would be to define a "mock-up" class, for example:

public class MyTest
{
    @Test
    public void testDoSomething()
    {
        new MockUp<ClassWhichDependsOnOtherBundles>()
        {
            @Mock
            void secondThing()
            {
               // do anything here
            }
        };

        new ClassWhichDependsOnOtherBundles().doSomething();
    }
}

Only the secondThing() method in the mocked class will be replaced by JMockit. The JMockit Expectations API could also be used, with partial mocking.

Rogerio
this looks interesting. in some cases i missed this in EasyMock, where afaik only a complete class can be mocked.
manuel aldana
Yes, the "JMockit Annotations" API always does partial mocking, based on the `@Mock` methods specified by the user.The EasyMock API is similar to the "JMockit Expectations" API; both of them also support partial mocking, although EasyMock requires the names of methods to be mocked in strings (for *partial* mocking, that is).
Rogerio
I think this most completely answers the question as asked, although I appreciate the suggested de-coupling solutions and may re-design
James Carr