为什么测试基于 class 的 autowire 注解会抛出 null 异常?

Why tested class based autowire annotation throw null exception?

我在我的项目中使用 Spring Boot 5 和 JUnit。我创建了一个单元测试来测试服务。

这是我正在测试的服务:

@Service
@RequiredArgsConstructor
@Slf4j
public class BuilderServiceImpl implements BuilderService{

    @Autowired
    public AutoMapper autoMapper;

    private final BuilderRepository builderRepository;
    private final AdminUserRepository adminUserRepository;


    @Override
    public BuilderDto getByEmail(String email){
    }

    @Override
    public List<BuilderMinDto> getAll() {}

    @Override
    public List<BuilderMinDto> getAll(int page, int size) {}

    @Override
    public SaveBuilderResponse create(Builder builder){

        var str = autoMapper.getDummyText();

        Builder savedBuilder = builderRepository.save(builder);

        return new SaveBuilderResponse(savedBuilder);
    }
}

下面是测试上述服务的测试 class:

@SpringBootTest
@RequiredArgsConstructor
@Slf4j
class BuilderServiceImplTest {

    @Mock
    private BuilderRepository builderRepository; 
    @Mock
    private AdminUserRepository adminUserRepository; 

    private  AutoCloseable autoCloseable;
    private  BuilderService underTest;

    @BeforeEach
    void setUp(){
        autoCloseable = MockitoAnnotations.openMocks(this);
        underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
    }

    @AfterEach
    void tearDown () throws Exception{
        autoCloseable.close();
    }

    @Test
    void getByEmail(){}

    @Test
    @Disabled
    void getAll() { }


    @Test
    @Disabled
    void testGetAll() {}

    @Test
    void create() {
        //given
        Builder builder = new Builder();
        builder.setName("John Johnson");
        builder.setCompanyName("Builders Test");
        builder.setEmail("test@builders.com");

        //when
        underTest.create(builder);

        //then
        ArgumentCaptor<Builder> builderArgumentCaptor = ArgumentCaptor.forClass(Builder.class);

        verify(builderRepository)
                .save(builderArgumentCaptor.capture());

        Builder captureBuilder = builderArgumentCaptor.getValue();

        assertThat(captureBuilder).isEqualTo(builder);
    }
}

当我开始 运行 测试时,class BuilderServiceImpl 中的创建方法被触发并在这一行:

        var str = autoMapper.getDummyText();
        

我得到 NullPointerException(autoMapper 实例为空)。

这里是AutoMapper的定义class:

@Component
@Slf4j
@RequiredArgsConstructor
public class AutoMapper {

    public String getDummyText(){
        return  "Hello From AutoMapper.";
    }
}

如您所见,我使用 @Component 注释将 AutoMapper class 注册到 IoC 容器,并使用 Autowired 注释将其注入 BuilderServiceImpl 属性 中的 autoMapper class。

为什么autoMapper实例为空?如何让autoMapper初始化?

在以下字段中添加 @Autowire 注释注释。由于您未在 BuilderServiceImpl

中初始化以下对象而出错
@Autowire
private final BuilderRepository builderRepository;

@Autowire
private final AdminUserRepository adminUserRepository;

为了使 @Autowire 工作,您必须使用 spring 本身创建的 BuilderServiceImpl(被测对象)的实例。

当您像这样创建对象时(自己手动创建):

 @BeforeEach
    void setUp(){
      ....
        underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
    }

Spring 对该对象一无所知,因此自动装配将不起作用

另一件可能有用的事情:

您已将 @Mock 用于 BuilderRepositoryAdminUserRepository。 这是普通的 mockito 注释,如果您使用 integration/system 测试在引擎盖下运行 spring,这可能不是您想要的:

当然,它会创建一个模拟,但不会将其放入应用程序上下文中,也不会替换可能由 spring 创建的这些 类 的 bean .

因此,如果这是您想要实现的目标,您应该改用 @MockBean。 此注释属于 Spring 测试框架,而不是普通的 mockito 注释。

总而言之,您可能会得到这样的结果:

@SpringBootTest 
class MyTest
{
   @MockBean
   BuilderRepository builderRepo;
   @MockBean
   AdminUserRepository adminUserRepo;

   @Autowired  // spring will inject your mock repository implementations
               // automatically 
   BuilderServiceImpl underTest;

   @Test
   void mytest() {
      ...
   }
}

为什么要手动创建 BuildService?如果这样做,请手动将 AutoMapper 设置为。

@BeforeEach
    void setUp(){
        autoCloseable = MockitoAnnotations.openMocks(this);
        underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
        underTest.setAutoMapper(new AutoMapper());
    }

您没有使用 di。