如何防止 Spring 在模拟中注入 @Autowired 引用?
How to prevent Spring from injecting @Autowired references inside a mock?
我想使用 Spring + JUnit + Mockito 测试 class,但我无法使其正常工作。
假设我的 class 引用了一个服务:
@Controller
public class MyController
{
@Autowired
private MyService service;
@PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
并且此服务引用存储库:
@Service
public class MyService {
@Autowired
private MyRepository repository;
public void whatever() {}
public void create() {
repository.save();
}
}
在测试 MyController class 时,我希望该服务被模拟。问题是:即使服务被模拟,Spring 也会尝试在模拟中注入存储库。
这是我所做的。测试 class:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { MyControllerTestConfiguration.class })
public class MyControllerTest {
@Autowired
private MyController myController;
@Test
public void testDoSomething() {
myController.doSomething();
}
}
配置class:
@Configuration
public class MyControllerTestConfiguration {
@Bean
public MyController myController() {
return new MyController();
}
@Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
}
我得到的错误是:org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [test.test.MyRepository] found for dependency
我尝试使用 Mockito 的 @InjectMocks
注释来初始化 mock,但这失败了,因为在 mocks 注入之前调用了 @PostConstruct
方法,生成了 NullPointerException
.
而且我不能简单地模拟存储库,因为在现实生活中这会让我模拟很多 classes...
谁能帮我解决这个问题?
你应该把@InjectMocks注解放在你的控制器里,@Mock注解放在你的服务里,看:
@Autowired
@InjectMocks
private MyController myController;
@Autowired
@Mock
private MyService myService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testDoSomething() {
myController.doSomething();
}
使用构造函数而不是字段注入。这使测试变得容易得多。
@Service
public class MyService {
private final MyRepository repository;
@Autowired
public MyService(MyRepository repository) {
this.repository = repository;
}
public void whatever() {}
public void create() {
repository.save();
}
}
-
@Controller
public class MyController {
private final MyService service;
@Autowired
public MyController(MyService service) {
this.service = service;
}
@PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
这有几个优点:
- 您的测试不需要 Spring。这允许您进行适当的 unit 测试。它还使测试速度非常快(从几秒到几毫秒)。
- 您不能不小心创建一个没有依赖项的 class 实例,否则会导致
NullPointerException
。
- 正如@NamshubWriter 指出的那样:
[The instance fields for the dependencies] can be final, so 1) they cannot be accidentally modified, and 2) any thread reading the field will read the same value.
丢弃 @Configuration
class 并编写如下测试:
@RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {
@Mock
private MyRepository repository;
@InjectMocks
private MyService service;
@Test
public void testDoSomething() {
MyController myController = new MyController(service);
myController.doSomething();
}
}
使用接口,特别是如果您使用某种 AOP(事务、安全等),即您将拥有接口 MyService
和 class MyServiceImpl
.
在配置中你将拥有:
@Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
我想使用 Spring + JUnit + Mockito 测试 class,但我无法使其正常工作。
假设我的 class 引用了一个服务:
@Controller
public class MyController
{
@Autowired
private MyService service;
@PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
并且此服务引用存储库:
@Service
public class MyService {
@Autowired
private MyRepository repository;
public void whatever() {}
public void create() {
repository.save();
}
}
在测试 MyController class 时,我希望该服务被模拟。问题是:即使服务被模拟,Spring 也会尝试在模拟中注入存储库。
这是我所做的。测试 class:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { MyControllerTestConfiguration.class })
public class MyControllerTest {
@Autowired
private MyController myController;
@Test
public void testDoSomething() {
myController.doSomething();
}
}
配置class:
@Configuration
public class MyControllerTestConfiguration {
@Bean
public MyController myController() {
return new MyController();
}
@Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
}
我得到的错误是:org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [test.test.MyRepository] found for dependency
我尝试使用 Mockito 的 @InjectMocks
注释来初始化 mock,但这失败了,因为在 mocks 注入之前调用了 @PostConstruct
方法,生成了 NullPointerException
.
而且我不能简单地模拟存储库,因为在现实生活中这会让我模拟很多 classes...
谁能帮我解决这个问题?
你应该把@InjectMocks注解放在你的控制器里,@Mock注解放在你的服务里,看:
@Autowired
@InjectMocks
private MyController myController;
@Autowired
@Mock
private MyService myService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testDoSomething() {
myController.doSomething();
}
使用构造函数而不是字段注入。这使测试变得容易得多。
@Service
public class MyService {
private final MyRepository repository;
@Autowired
public MyService(MyRepository repository) {
this.repository = repository;
}
public void whatever() {}
public void create() {
repository.save();
}
}
-
@Controller
public class MyController {
private final MyService service;
@Autowired
public MyController(MyService service) {
this.service = service;
}
@PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
这有几个优点:
- 您的测试不需要 Spring。这允许您进行适当的 unit 测试。它还使测试速度非常快(从几秒到几毫秒)。
- 您不能不小心创建一个没有依赖项的 class 实例,否则会导致
NullPointerException
。 - 正如@NamshubWriter 指出的那样:
[The instance fields for the dependencies] can be final, so 1) they cannot be accidentally modified, and 2) any thread reading the field will read the same value.
丢弃 @Configuration
class 并编写如下测试:
@RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {
@Mock
private MyRepository repository;
@InjectMocks
private MyService service;
@Test
public void testDoSomething() {
MyController myController = new MyController(service);
myController.doSomething();
}
}
使用接口,特别是如果您使用某种 AOP(事务、安全等),即您将拥有接口 MyService
和 class MyServiceImpl
.
在配置中你将拥有:
@Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}