如何防止 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);
    }