【问题标题】:@Autowired field null when executing production code via integration tests通过集成测试执行生产代码时 @Autowired 字段 null
【发布时间】:2020-02-12 11:03:04
【问题描述】:

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

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

服务类

@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);
    }

}

【问题讨论】:

  • 如何在集成测试中配置注入?
  • 无外部配置,例如使用@SpringBootTest。
  • 然后阅读 Erunafailaro 的回答。测试和运行配置之间必须存在差异,否则它会起作用。

标签: java spring-boot spring-data-jpa grpc-java


【解决方案1】:

在测试中调用new CustomerService()。您自己创建一个对象,而不是通过弹簧。我想你应该在测试类中创建一个字段

@Autowired private final CustomerService customerService

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

【讨论】:

  • 是的,你是对的!不知道我是怎么错过的,但是 gRPC 框架还有另一种实例化 CustomerService 的方法,因此它可以由 Spring 管理。谢谢。
【解决方案2】:

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

您可以使用这些功能:

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

在您的测试课程中,您必须使用@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 应该填充虚假数据以进行测试。

注意::为类特别是测试类有一个命名约定非常好。一个常见的用途是总是给 xxTest 的后缀,就像我在答案中所做的那样

【讨论】:

  • 感谢您的回复,但我正在设置集成测试,所以不想模拟部分软件。
  • 其实后缀xxIT.java是集成测试常用的命名规则。 Maven Failsafe Plugin 使用这个后缀作为集成测试的默认后缀:maven.apache.org/surefire/maven-failsafe-plugin/examples/…
  • 你必须这样做!这些 bean 是注入的依赖项,在某些时候必须被模拟。
【解决方案3】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-05-28
    • 1970-01-01
    • 2014-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-28
    相关资源
    最近更新 更多