由工厂类创建的对象注入模拟数据

18

我有下面这个类:

public class MyClass {        
    private Apple apple;

    public void myMethod() {
       apple = AppleFactory.createInstance(someStringVariable);
       ....
       ....
       ....
    }
}

还有测试类:

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {

        @InjectMocks 
        MyClass myClass;

        @Test
        public void myMethod(){
         ...
         ...
         ...
        }
    }

我该如何将一个苹果实例注入到MyClass中作为模拟对象?


关于 Avi & Ev0oD 的第一个答案,抽象类只能被扩展而不能被实现,即 public abstract class AppleFactory { public abstract Apple createInstance(final String str); } public class AppleFactoryImpl extends AppleFactory { public Apple createInstance(final String str) { // Implementation } } - ugurkocak1980
2个回答

29

你有三种解决方法:

抽象工厂:不要使用静态方法,而是使用一个具体的工厂类:

public abstract class AppleFactory {
    public Apple createInstance(final String str);
}

public class AppleFactoryImpl implements AppleFactory {
    public Apple createInstance(final String str) { // Implementation }
}

在你的测试类中,模拟工厂:

@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: 使用PowerMock创建静态方法的模拟对象。请查看我回答相关问题的答案,了解如何完成此操作。

可测试的类: 将Apple创建包装在一个protected方法中,并创建一个测试类来覆盖它:

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;
       }
    }
}

当然,在你的测试类中,你应该测试TestableMyClass而不是MyClass

我会告诉你我的每个方法的看法:

  1. 抽象工厂方法是最好的选择——这是一个清晰的设计,隐藏了实现细节

  2. 可测试类- 是第二个选项,需要最少的更改。

  3. PowerMock选项是我最不喜欢的 - 与其寻求更好的设计,还不如忽略和隐藏问题。但这仍然是一个有效的选项。

太棒了,谢谢。在我的情况下,我必须使用Mockito,所以我会选择Abstract Factory或TestableClass选项。 - saravana_pc
1
@saravana_pc - 我已将我的排名添加到问题中。不过,你可以使用 mockito 和 power mock。你可以使用它们的等效方法来替代 @Mock@InjectMock,这样就可以摆脱 @RunWith(MockitoJUnitRunner.class) 的声明了。 - Avi
3
实现抽象工厂模式时,使用接口会更合适吗? - Haim Bendanan
我不理解抽象类的必要性,仅使用实现不就可以了吗? 另外,虽然我喜欢这个想法,但它某种程度上剥夺了工厂的目的(你需要创建一个工厂实例)。现在Mockito可以模拟静态方法,但我不能使用更新的版本 :/ - Milan

0
除了Avi提出的解决方案之外,您还可以选择第四种可能性:
注入到工厂: 对于我来说,当您已经有要重构的代码时,这是最好的选择。使用此解决方案,您无需更改生产代码,只需更改工厂类和测试即可。
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;
    }
}

现在你可以简单地使用静态工厂:

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {

    @Mock
    private Apple appleMock;

    @InjectMocks 
    MyClass myClass;

    @Before
    public void setup() {
        AppleFactory.setInjectedApple(appleMock);
    }

    @Test
    public void myMethod(){
     ...
     ...
     ...
    }
}

那你如何测试AppleFactory呢? - Ciberman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接