问题
I have the following class:
public class MyClass {
private Apple apple;
public void myMethod() {
apple = AppleFactory.createInstance(someStringVariable);
....
....
....
}
}
And the Test class:
@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {
@InjectMocks
MyClass myClass;
@Test
public void myMethod(){
...
...
...
}
}
How could I inject an Apple instance as a mock in MyClass?
回答1:
You have 3 possibilities to solve this:
Abstract factory: Instead of using a static method, use a concrete factory class:
public abstract class AppleFactory {
public Apple createInstance(final String str);
}
public class AppleFactoryImpl implements AppleFactory {
public Apple createInstance(final String str) { // Implementation }
}
In your test class, mock the factory:
@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {
@Mock
private AppleFactory appleFactoryMock;
@Mock
private Apple appleMock;
@InjectMocks
MyClass myClass;
@Before
public void setup() {
when(appleFactoryMock.createInstance(Matchers.anyString()).thenReturn(appleMock);
}
@Test
public void myMethod(){
...
...
...
}
}
PowerMock: Use PowerMock to create a mock of a static method. Look at my answer to a relevant question to see how it's done.
Testable class: Make the Apple
creation wrapped in a protected
method and create a test class that overrides it:
public class MyClass {
private Apple apple;
public void myMethod() {
apple = createApple();
....
....
....
}
protected Apple createApple() {
return AppleFactory.createInstance(someStringVariable);
}
}
@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {
@Mock
private Apple appleMock;
@InjectMocks
MyClass myClass;
@Test
public void myMethod(){
...
...
...
}
private class TestableMyClass extends MyClass {
@Override
public void createApple() {
return appleMock;
}
}
}
Of course, in your test class you should test TestableMyClass
and not MyClass
.
I'll tell you my opinion on each of the methods:
The abstract factory method is the best one - This is a clear design that hides the implementation details
The testable class - Is the second option which requires minimum changes
- The
PowerMock
option is my least favorite - Instead of going for a better design, you ignore and hide your problem. But that's still a valid option.
回答2:
Regarding the first answer of Avi & Ev0oD. Abstract classes can only be extended and not implemented.
public abstract class AppleFactory {
public abstract Apple createInstance(final String str);
}
public class AppleFactoryImpl extends AppleFactory {
public Apple createInstance(final String str) { // Implementation }
}
回答3:
In addition of the solution proposed by Avi, you can choose a fourth possibility:
Inject into Factory: This is, for me, the best option when you already have code to refacrot. With this solution you don't have to change porduction code but only factory class and test.
public class AppleFactory
{
private static Apple _injectedApple;
public static createInstance(String str)
{
if (_injectedApple != null)
{
var currentApple = _injectedApple;
_injectedApple = null;
return currentApple;
}
//standard implementation
}
public static setInjectedApple(Apple apple)
{
_injectedApple = apple;
}
}
Now you can use your static factory simply:
@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {
@Mock
private Apple appleMock;
@InjectMocks
MyClass myClass;
@Before
public void setup() {
AppleFactory.setInjectedApple(appleMock);
}
@Test
public void myMethod(){
...
...
...
}
}
来源:https://stackoverflow.com/questions/21879804/inject-mocks-for-objects-created-by-factory-classes