代码之家  ›  专栏  ›  技术社区  ›  Thirukka Karnan

模拟最终类并将其注入autowired数据成员并克服postConstruct方法调用

  •  1
  • Thirukka Karnan  · 技术社区  · 8 年前

    我想用一个autowired final class对象对一个java类进行单元测试,以及用@PostConstruct方法对另一个autowired class进行单元测试。虽然可以单独测试它们,但我无法将它们组合在一起。

    这个问题是关于 injecting mockito mocks into spring bean

    待测试代码

    public class A { 
        @Autowired
        private FinalClass serviceClient;
        @Autowired
        private ClassWithPostConstructor resourceVerifier;
    
        //no setters or constructors
    
        public String useBothFinalClassAndClassWithPostConstructor() {
            //logic to be tested
        }
    }
    

    工作试验等级

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(FinalClass.class)
    public class ATest {
        //@org.mockito.Mock //fails to mock final class
        @org.powermock.api.easymock.annotation.Mock
        private FinalClass serviceClient;
        @org.powermock.api.easymock.annotation.Mock
        private ClassWithPostConstructor resourceVerifier;
        //other mock objects required for mocking the services
    
        //@InjectMocks //fails since mocking final class
        private A a;
    
        @Before
        public void init() {
            a = new A();
            //working snippet with setters created in A and without @Autowired here within the test
            serviceClient = PowerMock.create(FinalClass.class);
            a.setServiceClient(serviceClient);
            resourceVerifier = PowerMock.create(ClassWithPostConstructor.class);
            a.setClassWithPostConstructor(resourceVerifier);
        }
    
        @Test
        public void testTheMethodUsingExpectAndVerify() {
            //test the functionality here
            EasyMock.expect(serviceClient.callService()).andReturn("someMock");
            EasyMock.expect(resourceVerifier.verifyFn()).andReturn("success");
            PowerMock.replayAll();
            A.useBothFinalClassAndClassWithPostConstructor();
            PowerMock.verifyAll();
        }
    }
    

    上述代码适用于文件中的setter

    预期测试等级

    @RunWith(PowerMockRunner.class)
    @PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations={"file:spring-configuration/unit-testing-config.xml"})
    @PrepareForTest(FinalClass.class)
    public class ATest {
        @Autowired
        private FinalClass serviceClient;
        @Autowired
        private ClassWithPostConstructor resourceVerifier;
        //other mock objects required for mocking the services
    
        private A a;
    
        @Before
        public void init() {
            a = new A();
        }
    
        @Test
        public void testTheMethodUsingExpectAndVerify() {
            //test the functions here
            EasyMock.expect(serviceClient.callService()).andReturn("someMock");
            EasyMock.expect(resourceVerifier.verifyFn()).andReturn("success");
            PowerMock.replayAll();
            A.useBothFinalClassAndClassWithPostConstructor();
            PowerMock.verifyAll();
        }
    }
    
    //spring-configuration/unit-testing-config.xml
    //same error even on customer factory
    <bean id="resourceVerifier" class="org.powermock.api.easymock.PowerMock" factory-method="createMock">
        <constructor-arg type="java.lang.Class" value="com.company...resourceVerifier" /> 
    </bean>
    <bean id="resourceVerifier" class="org.powermock.api.easymock.PowerMock" factory-method="createMock">
        <constructor-arg type="java.lang.Class" value="com.company...serviceClient" /> 
    </bean>
    

    上面的代码段模拟了finalClass,但调用了ResourceVerifier的@PostConstructor。class-在这里应该做些什么来克服这个问题。


    调查

    • 可以使用 @注射模型 而不需要弹簧上下文配置。
    • @注射模拟 对于静态和最终字段,它会自动失败,当失败时,它也不会注入其他模拟。
    • 可以使用 PowerMock公司 的createMock并使用运行测试 PowerMockRunner @准备测试 .但这需要新的不必要的 设置器 为注入模拟 @自动连线 领域。
    • MockitoAnnotations.@Mock 与PowerMock配合不好(特别是在模拟最终类对象时),可以通过 EasyMock.Annotations.@Mock
    • EasyMock公司 PowerMock公司 没有 @注射模型 注解来尽可能地注入Mockito的模拟(本可以在几秒钟内解决问题)。
    • 可以通过 弹簧JUnit4Runner 以及单独的单元测试 @上下文配置
    • 可以同时运行相同的测试文件 PowerMockRunner 弹簧JUnit4Runner 具有 PowerMockRunnerDelegate
    • 我知道 @后期构造 若在代码中进行模拟,而不是使用springbean创建和注入,则不会自动执行该方法。
    • 如果编写包装工厂bean类并用于创建模拟,它会自动注入,但同时调用@PostConstruct方法。
    • 不可能依赖Springockito,因为它在这个阶段是不可靠的。

    但这些都不起作用,因为用例是所有这些的组合。


    可能的解决方案

    • 去除 @自动连线 字段和用途 凝固剂注入 这样就可以通过使用PowerMock(Tested to work)进行正常的模拟了。但这是一个惯例,外部团队包遵循这个惯例。
    • 或将@Autowired设置为setter或构造函数

    选择?

    我不认为这些课程需要重新组织,因为它们符合自己的目的,而且设计得很好。

    • 任何其他不需要对测试中的类进行操作的方法-如果我没有修改该类的权限怎么办?i、 例如,纯测试库依赖解决方案。
    • 不确定是否可以通过 PowerMockito公司 ? 还没有尝试过 PowerMockito与PowerMock .
    1 回复  |  直到 7 年前
        1
  •  1
  •   Arthur Zagretdinov    7 年前

    隐马尔可夫模型,

    不确定PowerMockito是否可行?还没有尝试过PowerMockito和PowerMock的组合。

    在我看来,你脑子里乱七八糟,误解了PowerMock/Mockito和EasyMock。

    你不应该同时使用 PowerMockito PowerMock ,因为这两个类是两个不同模拟框架的PowerMock友好API: EasyMock Mockito .没有理由同时使用它们。

    当然,这需要工作

    //@org.mockito.Mock //fails to mock final class
    @org.powermock.api.easymock.annotation.Mock
    private FinalClass serviceClient;
    @org.powermock.api.easymock.annotation.Mock
    private ClassWithPostConstructor resourceVerifier;
    //other mock objects required for mocking the services
    
    //@InjectMocks //fails since mocking final class
    private A a;
    

    因为,您通过EasyMock API减速并创建模拟,但尝试用Mockito Annotation注入它。

    你必须只选择一个模拟框架,并为其使用合适的API。目前,Mockito+PowerMockito(Mockito的PowerMock API)更适合您的需求。

    你可以完整地举例说明它的工作原理 PowerMock github

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(FinalClass.class)
    public class SpringInjectFinalClassExampleTest {
    
        @Mock
        private FinalClass finalClass;
    
        @InjectMocks
        private MyBean myBean = new MyBean();;
    
        @Test
        public void testInjectFinalClass() {
            final String value = "What's up?";
            when(finalClass.sayHello()).thenReturn(value);
    
            assertEquals(value, myBean.sayHello());
    
        }
    
    }