英文:
Self injection in spring and testing it using mockito
问题
我们正在使用Spring 5.2.x(与Spring Boot 2.3.x)和Mockito 3.3.x。
我需要在我的服务中从中调用一个@Transactional
方法,因此我不得不采用自我注入。
@Service
@RequiredArgsConstructor // lombok
public class MyClass {
private final dep1;
private final dep2;
@Autowired
private MyClass self;
public void someMethod() {
self.someTransactionalMethod();
// do something
}
@Transactional
public void someTransactionalMethod() {
// do something
}
}
public class MyClassTest {
@Mock
private dep1;
@Mock
private dep2;
@InjectMocks
private MyClass myClass;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void someMethodTest() {
// 调用self.someTransactionalMethod()时会出现NPE
}
}
从代码中可以看出,我们正在使用Lombok进行构造函数注入。出于明显的原因,我不能将实例变量self
声明为final
,因此对于这个变量使用了@Autowired
。该类正常工作,但我在单元测试中遇到了问题。
我的问题有两个部分:
- 关于我进行自我注入的方式 - 这是否是最佳的自我注入方式?在Spring的最新版本中,作者是否推荐其他方法?
- Mockito无法将self bean注入到CUT(Class Under Test)的对象中。有没有一种干净的方式来实现这一点?
我已经考虑了以下几种方法:
- 引入一个用于
self
的setter,并在设置方法中使用它来注入self。不想为测试添加一个setter。 - 使用
ReflectionUtils
来设置self
。反射对我来说似乎不够清晰。我更喜欢使用@Setter
来代替这种方式。 - 尝试使用
@RunWith(SpringJUnit4ClassRunner.class)
,但是预计会失败,因为我没有创建适当的上下文等。
英文:
We are using Spring 5.2.x (with Spring Boot 2.3.x) & Mockito 3.3.x.
I need to call a @Transactional
method in my service from within it. Hence I had to resort to self injection.
@Service
@RequiredArgsCostructor // lombok
public class MyClass {
private final dep1;
private final dep2;
@Autowired
private MyClass self;
public void someMethod() {
self.someTransactionalMethod();
// do something
}
@Transactional
public void someTransactionalMethod() {
// do something
}
}
public class MyClassTest {
@Mock
private dep1;
@Mock
private dep2;
@InjectMocks
private MyClass myClass;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void someMethodTest() {
// NPE when calling self.someTransactionalMethod()
}
}
From the code it is relevant that we are using constructor injection using lombok. For obvious reasons, I cannot declare the instance variable self
as final
, hence using @Autowired
for this one. The class works fine but I see issues with the unit test.
There are 2 parts to my question -
- Regarding the way I am doing self injection - is this the best way of doing self injection? Is there any other approach recommended by the authors of Spring framework in recent versions of Spring?
- Mockito is by no means able to inject the self bean into the object of CUT (Class Under Test). Is there a way to achieve this in a clean way?
I have already considered the following:
- Introduce a setter for
self
and use it to inject self in setup method. Don't want to add a setter only for tests. - Use
ReflectionUtils
to setself
. Reflection seems unclean to me. I would prefer@Setter
over this. - Tried using
@RunWith(SpringJunit4Runner.class)
but that failed as expected because I am not creating a proper context and stuff.
答案1
得分: 5
In setUp() 方法调用 ReflectionTestUtils.setField(myClass, "self", myClass)
英文:
In setUp() method call ReflectionTestUtils.setField(myClass, "self", myClass)
答案2
得分: 0
If injecting self references is difficult or you need ReflectionTestUtils to fulfill it, it is because the test is not a pure unit test and in your case because the service does not have a well defined interface. If we change the service to implement an interface and change the test to be a pure unit test, the code becomes as follows and the problems disappear:
public class MyClass implements MyInterface {
@Inject
private MyInterface self;
@Override
public void someMethod() {
self.someTransactionalMethod();
}
@Override
@Transactional
public Object someTransactionalMethod() {
throw new UnsupportedOperationException("Not implemented yet");
}
}
And the test becomes:
@RunWith(MockitoJunitRunner.class)
public class MyClassTest {
@Mock
private MyInterface self;
@InjectMocks
private MyClass service;
@Test
public void someMethodShouldCallSelf() {
when(self.someTransactionalMethod()).thenReturn("test");
service.someMethod();
verify(self, times(1)).someTransactionalMethod();
}
}
Now the test for someMethod()
follows unit testing principles by actually testing only the code in someMethod()
instead of including someTransactionalMethod()
in its complexity. Then you write separate unit tests for someTransactionalMethod()
to ensure that it functions correctly.
英文:
If injecting self references is difficult or you need ReflectionTestUtils to fulfill it, it is because the test is not a pure unit test and in your case because the service does not have a well defined interface. If we change the service to implement an interface and change the test to be a pure unit test, the code becomes as follows and the problems disappear:
public class MyClass implements MyInterface {
@Inject
private MyInterface self;
@Override
public void someMethod() {
self.someTransactionalMethod();
}
@Override
@Transactional
public Object someTransactionalMethod() {
throw new UnsupportedOperationException("Not implemented yet");
}
}
And the test becomes:
@RunWith(MockitoJunitRunner.class)
public class MyClassTest {
@Mock
private MyInterface self;
@InjectMocks
private MyClass service;
@Test
public void someMethodShouldCallSelf() {
when(self.someTransactionalMethod()).thenReturn("test");
service.someMethod();
verify(self, times(1)).someTransactionalMethod();
}
}
Now the test for someMethod()
follows unit testing principles by actually testing only the code in someMethod()
instead of including someTransactionalMethod()
in it's complexity. Then you write separate unit tests for someTransactionalMethod()
to ensure that it functions correctly.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论