通过集成测试执行生产代码时@Autowired 字段为空

@Autowired field null when executing production code via integration tests

过去几天我一直在摸不着头脑的一个奇怪的问题。我有一个现场注入到服务 class 中的 JPA 存储库。当 运行 服务器并通过客户端发送请求但是当通过集成测试执行代码时,注入的字段 class (CustomerRepository) 始终为空。

我已经通过互联网尝试了各种建议,但我没有找到与我相似的情况,任何帮助将不胜感激

服务class

@GRpcService
public class CustomerService extends CustomerServiceGrpc.CustomerServiceImplBase {

    @Autowired
    private CustomerRepository repository;

    @Override
    public void createCustomer(CreateCustomerRequest request, StreamObserver<CreateCustomerResponse> responseObserver) {

        final CustomerDao convertedDao = ProtoToDaoConverter.convertCustomerRequestProtoToCustomerDao(request);

        repository.save(convertedDao);

        responseObserver.onNext(CreateCustomerResponse.newBuilder().setSuccess(true).build());
        responseObserver.onCompleted();
    }
}

集成测试

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class CustomerServiceIT {

    @Rule
    private final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();

    @Test
    public void something() throws IOException {

        String serverName = InProcessServerBuilder.generateName();

        // Create a server, add service, start, and register for automatic graceful shutdown.
        grpcCleanup.register(InProcessServerBuilder
                .forName(serverName).directExecutor().addService(new CustomerService()).build().start());

        customerServiceGrpc.CustomerServiceBlockingStub blockingStub = CustomerServiceGrpc.newBlockingStub(
                // Create a client channel and register for automatic graceful shutdown.
                grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()));

        final CreateCustomerRequest request = CreateCustomerRequest.newBuilder().setFirstName("Simon").setSecondName("Brown").setRole("Product Developer").build();

        final CreateCustomerResponse response = blockingStub.createCustomer(request);
    }

}

您可以使用 Mockito 或任何其他测试框架来模拟您的 class 依赖项(您的服务和 JPA 存储库)。

您可以使用这些功能:

  • @InjectMocks - 实例化测试对象实例并尝试将用 @Mock@Spy 注释的字段注入到测试对象的私有字段中
  • @Mock - 创建它注释的字段的模拟实例

在你的测试中 class 你必须使用 @Mock 来注入“一个假的存储库”:

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class CustomerServiceTest {

    @InjectMocks
    private CustomerService testingObject;

    @Mock
    private CustomerRepository customRepository;

    @Rule
    private final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();

    @BeforeMethod
    public void initMocks(){
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void something() throws IOException {

    // inside the testing method you have to define what you mocked object should return.
    // it means mocking the CustomRepository methods

    CustomerDao customer = new CustomerDao(); // fill it with data
    Mockito.when(customRepository.save()).thenReturn(customer);


        // Create a server, add service, start, and register for automatic graceful shutdown.
        grpcCleanup.register(InProcessServerBuilder
                .forName(serverName).directExecutor().addService(new CustomerService()).build().start());

        customerServiceGrpc.CustomerServiceBlockingStub blockingStub = CustomerServiceGrpc.newBlockingStub(
                // Create a client channel and register for automatic graceful shutdown.
                grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()));

        final CreateCustomerRequest request = CreateCustomerRequest.newBuilder().setFirstName("Simon").setSecondName("Brown").setRole("Product Developer").build();

        final CreateCustomerResponse response = blockingStub.createCustomer(request);
    }

}

由于您的存储库及其方法是模拟的,因此您的 customerService 应该填充虚假数据以用于测试目的。

注意: :很高兴为 classes 制定命名约定,尤其是测试 classes。一个常见的用法是总是给 xxTest 的后缀,就像我在答案中所做的那样

如果无法读取堆栈跟踪,则很难回答。我会通过查看 spring 上下文配置 类 来开始调查这个问题。也许其中一些在您的集成测试运行时丢失了。

如果您的应用程序中存在 spring @Configuration 或其他 @Component 类,可能有助于在您的测试中显式加载它们,以及您意外的 null-值 CustomerRepository:

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {AppConfig.class, CustomerRepository.class })
public class CustomerServiceIT {

这可能无法解决问题,但可能会揭示一些有助于您调查问题的错误消息。

在测试中调用 new CustomerService()。您自己创建一个对象,而不是通过 spring。我猜你应该在 test class

中创建一个字段

@Autowired private final CustomerService customerService

并传入 grpcCleanup.register(InProcessServerBuilder .forName(serverName).directExecutor().addService(customerService).build().start());