Self injection in spring and testing it using mockito.

huangapple go评论101阅读模式
英文:

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。该类正常工作,但我在单元测试中遇到了问题。

我的问题有两个部分:

  1. 关于我进行自我注入的方式 - 这是否是最佳的自我注入方式?在Spring的最新版本中,作者是否推荐其他方法?
  2. Mockito无法将self bean注入到CUT(Class Under Test)的对象中。有没有一种干净的方式来实现这一点?

我已经考虑了以下几种方法:

  1. 引入一个用于self的setter,并在设置方法中使用它来注入self。不想为测试添加一个setter。
  2. 使用ReflectionUtils来设置self。反射对我来说似乎不够清晰。我更喜欢使用@Setter来代替这种方式。
  3. 尝试使用@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 -

  1. 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?
  2. 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:

  1. Introduce a setter for self and use it to inject self in setup method. Don't want to add a setter only for tests.
  2. Use ReflectionUtils to set self. Reflection seems unclean to me. I would prefer @Setter over this.
  3. 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.

huangapple
  • 本文由 发表于 2020年7月31日 15:01:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/63187187.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定