通过工厂方法将Mockito模拟注入豆类中

时间:2015-02-23 07:52:39

标签: java spring unit-testing mockito

我有以下设置:

在测试期间需要模拟的示例类:

@Component
class MyConfig
{
    public String getConfig()
    {
        return "RealValue";
    }
}

定义单个方法的接口,有两个实现:

interface MyInterface
{
    String myMethod();
}

class MyImpl1 implements MyInterface
{
    private final MyInterface delegate;
    private final MyConfig config;

    public MyImpl1(final MyInterface delegate, final MyConfig config)
    {
        this.delegate = delegate;
        this.config = config;
    }

    @Override
    public String myMethod()
    {
        return this.getClass().getSimpleName() + ": " + config.getConfig() + ", " + delegate.myMethod() + ";";
    }
}

class MyImpl2 implements MyInterface
{
    private final MyConfig config;

    public MyImpl2(final MyConfig config)
    {
        this.config = config;
    }

    @Override
    public String myMethod()
    {
        return this.getClass().getSimpleName() + ": " + config.getConfig();
    }
}

使用注入MyInterface的弹簧bean的MyConfig对象准备类型为MyFactory的bean的工厂类:

@Component
class MyFactory
{
    @Autowired
    private MyConfig config;

    // Factory method to create the bean.
    @Bean(name = "myInterface")
    protected MyInterface myInterface()
    {
        final MyImpl2 myImpl2 = new MyImpl2(config);
        final MyImpl1 myImpl1 = new MyImpl1(myImpl2, config);
        return myImpl1;
    }

    // A simple getter that prepares MyInterface on the fly.
    // This is just to demonstrate that getter picks the mock where as 
    // the factory bean doesn't
    public MyInterface getInterface()
    {
        final MyImpl2 myImpl2 = new MyImpl2(config);
        final MyImpl1 myImpl1 = new MyImpl1(myImpl2, config);
        return myImpl1;
    }
}

一个简单的测试用例,用于检查MyConfig的模拟版本是否进入使用@Bean创建的bean:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "classpath:application-context.xml"
})
public class SpringBeanMockExampleTest
{
    @Mock
    private MyConfig config;

    @InjectMocks
    @Autowired
    protected MyFactory factory;

    @Resource
    private MyInterface myInterface;

    @Before
    public void setupMocks()
    {
        MockitoAnnotations.initMocks(this);
    }

    /**
     * Fails as the return value is "MyImpl1: RealValue, MyImpl2: RealValue;"
     */
    @Test
    public void testBean()
    {
        Mockito.when(config.getConfig()).thenReturn("MockValue");
        Assert.assertEquals("MyImpl1: MockValue, MyImpl2: MockValue;", myInterface.myMethod());
    }

    /**
     * Assertion passes here.    
     */
    @Test
    public void testGetter()
    {
        Mockito.when(config.getConfig()).thenReturn("MockValue");
        Assert.assertEquals("MyImpl1: MockValue, MyImpl2: MockValue;", factory.getInterface().myMethod());
    }
}

我希望testBean方法也可以通过,但显然模拟不会被注入到MyFactory中创建的工厂bean中。

在工厂bean创建步骤完成后,模拟似乎正在替换实际的bean。因此,工厂bean中的引用不会使用mock更新。

如何解决此问题,以便testBean按预期工作?

1 个答案:

答案 0 :(得分:1)

这不会奏效。

首先初始化您的Spring上下文。然后执行TestExecutionListener来处理测试中的依赖注入(例如@Autowired)。

然后,在每个测试运行之前,您的@Before方法将在SpringBeanMockExampleTest的测试实例中初始化Mockito模拟,从而有效地覆盖自动连接的Spring依赖项。

为什么呢? Mockito为使用@InjectMocks@Mock@Spy@Captor注释的所有属性创建了一个新实例。

一种可能的解决方案是在工厂中手动设置模拟配置,而不是使用@InjectMocks,覆盖Spring配置bean。

@Before
public void setupMocks(){
    Config config = mock(Config.class);
    factory.setConfig(config);    
}

请注意,将模拟与(Spring)集成测试相结合是不良做法,因为模拟只能在单元测试中完成。

更好的设置是使用配置文件在Spring上下文中设置单独的Config bean,例如:

@Profile("testing")
@Component
public class TestConfig implements Config {
    public String getConfig(){
        return "testValue";    
    }
}