将Mockito模拟注入Spring bean

tea*_*bot 276 junit spring annotations dependency-injection mockito

我想将一个Mockito模拟对象注入一个Spring(3+)bean中,以便使用JUnit进行单元测试.我的bean依赖项目前通过@Autowired在私有成员字段上使用注释来注入.

我考虑过使用ReflectionTestUtils.setField但我希望注入的bean实例实际上是一个代理,因此不会声明目标类的私有成员字段.我不希望为依赖创建一个公共setter,因为我将修改我的界面纯粹是为了测试的目的.

我遵循了Spring社区给出的一些建议,但是没有创建模拟并且自动连线失败:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>
Run Code Online (Sandbox Code Playgroud)

我目前遇到的错误如下:

...
Caused by: org...NoSuchBeanDefinitionException:
    No matching bean of type [com.package.Dao] found for dependency:
    expected at least 1 bean which qualifies as autowire candidate for this dependency.
    Dependency annotations: {
        @org...Autowired(required=true),
        @org...Qualifier(value=dao)
    }
at org...DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(D...y.java:901)
at org...DefaultListableBeanFactory.doResolveDependency(D...y.java:770)
Run Code Online (Sandbox Code Playgroud)

如果我将constructor-arg值设置为无效,则在启动应用程序上下文时不会发生错误.

amr*_*mra 128

最好的方法是:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock"> 
    <constructor-arg value="com.package.Dao" /> 
</bean> 
Run Code Online (Sandbox Code Playgroud)

更新
在上下文文件中,必须在任何自动装配字段之前列出此模拟,具体取决于它的声明.

  • 不知道为什么这个答案得到了如此多的支持,结果bean无法自动装配,因为它的类型错误. (7认同)
  • @amra:spring dos不推断在这种情况下返回的对象的类型... http://stackoverflow.com/q/6976421/306488 (4认同)
  • 如果它在上下文文件中首先列出(在声明依赖于它的任何自动装配字段之前),则可以自动装配. (4认同)
  • 从3.2版开始,豆类的顺序不再重要.请参阅此博客文章中标题为"通用工厂方法"的部分:http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/ (3认同)

小智 107

@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

@Before
public void setup() {
        MockitoAnnotations.initMocks(this);
}
Run Code Online (Sandbox Code Playgroud)

这会将任何模拟对象注入测试类.在这种情况下,它会将mockedObject注入testObject.这是上面提到的,但这里是代码.

  • 仅供参考:如果我想在MyTestObject中进行部分自动装配和部分模拟,则此方法不起作用. (39认同)
  • 我不知道为什么这不会更高.如果我看到包含XML的更多答案,我将会投掷. (8认同)
  • 为什么不在这个`mockedObject`上使用`Mockito.spy(...)`呢?然后使用`when(mockedObject.execute).thenReturn(objToReturn)`或`doReturn(objToReturn).when(mockedObject).execute()`.第二个不要调用真正的方法.你也可以查看`Mockito.doCallRealMethod()`文档 (3认同)

Pio*_*zda 62

我有一个使用Spring Java Config和Mockito的非常简单的解决方案:

@Configuration
public class TestConfig {

    @Mock BeanA beanA;
    @Mock BeanB beanB;

    public TestConfig() {
        MockitoAnnotations.initMocks(this); //This is a key
    }

    //You basically generate getters and add @Bean annotation everywhere
    @Bean
    public BeanA getBeanA() {
        return beanA;
    }

    @Bean
    public BeanB getBeanB() {
        return beanB;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 有什么意义?为什么用`initMocks`添加带注释的字段和构造函数?为什么不在`getBeanA`中'返回Mockito.mock(BeanA.class)`?这种方式更简单,代码更少.我错过了什么? (6认同)
  • 出于某种原因,使用这种方法,spring试图创建实际的bean(而不是模拟)和choke on that ...我做错了什么? (4认同)
  • 如果您要嘲笑一个类,则不是spring而是模仿对象尝试实例化一个实际的bean。如果您有任何必须在测试中模拟的bean,则它们应该是接口的实现,并通过该接口注入。如果然后模拟该接口(而不是类),则模仿不会尝试实例化该类。 (3认同)

Pau*_*kin 44

鉴于:

@Service
public class MyService {
    @Autowired
    private MyDAO myDAO;

    // etc
}
Run Code Online (Sandbox Code Playgroud)

您可以通过自动装配加载正在测试的类,使用Mockito模拟依赖关系,然后使用Spring的ReflectionTestUtils将模拟注入正在测试的类中.

@ContextConfiguration(classes = { MvcConfiguration.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {
    @Autowired
    private MyService myService;

    private MyDAO myDAOMock;

    @Before
    public void before() {
        myDAOMock = Mockito.mock(MyDAO.class);
        ReflectionTestUtils.setField(myService, "myDAO", myDAOMock);
    }

    // etc
}
Run Code Online (Sandbox Code Playgroud)

请注意,春节前4.3.1,这种方法不会使用代理服务工作(带注释@Transactional,或者Cacheable,例如).这已由SPR-14050修复.

对于早期版本,解决方案是解包代理,如下所述:事务性注释避免了被模拟的服务(ReflectionTestUtils.setField现在默认情况下是这样)

  • 几乎一整天都在试图让@MockBean 在不重置上下文的情况下工作,然后我遇到了这个 gem。正是我需要的,干杯。 (2认同)

jfc*_*edo 35

如果你使用的是Spring Boot 1.4,它有一个很棒的方法.只需@SpringBootTest在你的类和@MockBean现场使用新品牌,Spring Boot将创建一个这种类型的模拟器,它会将它注入上下文(而不是注入原始的):

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {

    @MockBean
    private RemoteService remoteService;

    @Autowired
    private Reverser reverser;

    @Test
    public void exampleTest() {
        // RemoteService has been injected into the reverser bean
        given(this.remoteService.someCall()).willReturn("mock");
        String reverse = reverser.reverseSomeCall();
        assertThat(reverse).isEqualTo("kcom");
    }

}
Run Code Online (Sandbox Code Playgroud)

另一方面,如果您没有使用Spring Boot,或者您使用的是以前的版本,那么您将需要做更多的工作:

创建一个@Configuration将mocks注入Spring上下文的bean:

@Configuration
@Profile("useMocks")
public class MockConfigurer {

    @Bean
    @Primary
    public MyBean myBeanSpy() {
        return mock(MyBean.class);
    }
}
Run Code Online (Sandbox Code Playgroud)

@Primary如果没有指定限定符,则使用注释告诉spring该bean具有优先级.

确保您注释该类@Profile("useMocks")以控制哪些类将使用模拟以及哪些将使用真实bean.

最后,在您的测试中,激活userMocks配置文件:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebIntegrationTest
@ActiveProfiles(profiles={"useMocks"})
public class YourIntegrationTestIT {

    @Inject
    private MyBean myBean; //It will be the mock!


    @Test
    public void test() {
        ....
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你不想使用mock而不是真正的bean,那就不要激活useMocksprofile:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebIntegrationTest
public class AnotherIntegrationTestIT {

    @Inject
    private MyBean myBean; //It will be the real implementation!


    @Test
    public void test() {
        ....
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这个答案应该放在顶部 - 春季启动时@mockBean支持也可以在没有spring-boot的情况下使用.您只能在单元测试中使用它,因此它适用于所有弹簧应用! (5认同)
  • @Profile注释也可以在bean定义方法上设置,以避免创建单独的配置类 (2认同)

Dou*_*rop 19

由于1.8.3 Mockito有@InjectMocks - 这非常有用.我的JUnit测试是@RunWith MockitoJUnitRunner,我构建的@Mock对象满足所测试类的所有依赖关系,这些对象都是在私有成员使用@InjectMocks注释时注入的.

我@Run只使用SpringJUnit4Runner进行集成测试.

我会注意到它似乎无法以与Spring相同的方式注入List.它只查找满足List的Mock对象,并且不会注入Mock对象列表.我的解决方法是对手动实例化的列表使用@Spy,并手动.将模拟对象添加到该列表以进行单元测试.也许这是故意的,因为它肯定迫使我密切注意被嘲弄的东西.


tea*_*bot 13

更新:现在有更好,更清洁的解决方案来解决这个问题.请先考虑其他答案.

我终于在他的博客上找到了ronen的回答.我遇到的问题是由于Mockito.mock(Class c)声明返回类型的方法Object.因此Spring无法从工厂方法返回类型推断bean类型.

Ronen的解决方案是创建一个FactoryBean返回模拟的实现.该FactoryBean接口允许Spring查询工厂bean创建的对象类型.

我的模拟bean定义现在看起来像:

<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>
Run Code Online (Sandbox Code Playgroud)


Rya*_*lls 12

从Spring 3.2开始,这不再是一个问题.Spring现在支持自动装配通用工厂方法的结果.请参阅此博客文章中标题为"通用工厂方法"的部分:http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/.

关键点是:

在Spring 3.2中,现在可以正确推断出工厂方法的泛型返回类型,并且按类型自动装配模拟应该按预期工作.因此,可能不再需要自定义解决方法,如MockitoFactoryBean,EasyMockFactoryBean或Springockito.

这意味着这应该是开箱即用的:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>
Run Code Online (Sandbox Code Playgroud)


Mar*_*s T 9

如果您使用的是spring> = 3.0,请尝试使用Springs @Configuration注释来定义应用程序上下文的一部分

@Configuration
@ImportResource("com/blah/blurk/rest-of-config.xml")
public class DaoTestConfiguration {

    @Bean
    public ApplicationService applicationService() {
        return mock(ApplicationService.class);
    }

}
Run Code Online (Sandbox Code Playgroud)

如果您不想使用@ImportResource,也可以通过其他方式完成:

<beans>
    <!-- rest of your config -->

    <!-- the container recognize this as a Configuration and adds it's beans 
         to the container -->
    <bean class="com.package.DaoTestConfiguration"/>
</beans>
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请查看spring-framework-reference:基于Java的容器配置


小智 9

下面的代码适用于自动装配 - 它不是最短的版本,但它只适用于标准的spring/mockito jar时很有用.

<bean id="dao" class="org.springframework.aop.framework.ProxyFactoryBean">
   <property name="target"> <bean class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean> </property>
   <property name="proxyInterfaces"> <value>com.package.Dao</value> </property>
</bean> 
Run Code Online (Sandbox Code Playgroud)


Ang*_*ese 8

也许不是完美的解决方案,但我倾向于不使用弹簧来进行单元测试.单个bean(被测试的类)的依赖关系通常不会过于复杂,所以我只是直接在测试代码中进行注入.

  • 我理解你的方法.但是,我发现自己处于这种情况下的大型遗留代码库中,并不容易实现这一点. (3认同)

小智 7

我可以使用Mockito执行以下操作:

<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.abcd.StateMachine"/>
</bean>
Run Code Online (Sandbox Code Playgroud)


小智 6

基于上述方法发布一些示例

随着春天:

@ContextConfiguration(locations = { "classpath:context.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class TestServiceTest {
    @InjectMocks
    private TestService testService;
    @Mock
    private TestService2 testService2;
}
Run Code Online (Sandbox Code Playgroud)

没有春天:

@RunWith(MockitoJUnitRunner.class)
public class TestServiceTest {
    @InjectMocks
    private TestService testService = new TestServiceImpl();
    @Mock
    private TestService2 testService2;
}
Run Code Online (Sandbox Code Playgroud)