Java @从单元测试中使用@Service实例调用方法时,存储库实例为null

Java @从单元测试中使用@Service实例调用方法时,存储库实例为null,java,spring,spring-boot,unit-testing,jpa,Java,Spring,Spring Boot,Unit Testing,Jpa,我的目标是为这些单元测试使用内存中的数据库,这些相关性如下所示: implementation("org.springframework.boot:spring-boot-starter-data-jpa") runtimeOnly("com.h2database:h2") 因此,存储库实例实际上与数据库交互,我不只是模拟返回值 问题是,当我运行单元测试时,服务实例中的存储库实例是null 为什么呢?我是否缺少初始化存储库实例的单元测试类上的注释 这是运

我的目标是为这些单元测试使用内存中的数据库,这些相关性如下所示:

implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2")
因此,存储库实例实际上与数据库交互,我不只是模拟返回值

问题是,当我运行单元测试时,服务实例中的存储库实例是
null

为什么呢?我是否缺少初始化存储库实例的单元测试类上的注释

这是运行我的单元测试时的控制台输出:

null

java.lang.NullPointerException
    at com.my.MyService.findAll(MyService.java:20)
    at com.my.MyTest.testMy(MyTest.java:23)
我的单元测试类:

public class MyTest {

  @MockBean
  MyRepository myRepository;

  @Test
  void testMy() {
    MyService myService = new MyService();
    int size = myService.findAll().size();
    Assertions.assertEquals(0, size);
  }
}
我的服务级别:

@Service
public class MyService {

    @Autowired
    MyRepository myRepository;

    public List<MyEntity> findAll() {

        System.out.println(myRepository); // null
        return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
    }

    @Transactional
    public MyEntity create(MyEntity myEntity) {

        myRepository.save(myEntity);

        return myEntity;
    }
}

要使用
@MockBean
注释,必须使用SpringRunner来运行测试。在测试类顶部使用
@RunWith
注释并通过
SpringRunner.class

@RunWith(SpringRunner.class)
public class MyTest {

  @MockBean
  MyRepository myRepository;

  @Test
  void testMy() {
    MyService myService = new MyService();
    int size = myService.findAll().size();
    Assertions.assertEquals(0, size);
  }
}

要使用
@MockBean
注释,必须使用SpringRunner来运行测试。在测试类顶部使用
@RunWith
注释并通过
SpringRunner.class

@RunWith(SpringRunner.class)
public class MyTest {

  @MockBean
  MyRepository myRepository;

  @Test
  void testMy() {
    MyService myService = new MyService();
    int size = myService.findAll().size();
    Assertions.assertEquals(0, size);
  }
}

我相信您希望编写一个集成测试。在这里,您可以删除MockBean注释,只需自动连接存储库。此外,与SpringRunner课程一起跑步

@RunWith(SpringRunner.class)
public class MyTest {

  @Autowired
  MyRepository myRepository;

  @Autowired
  MyService myService
  
  @Test
  void testMy() {
   int size = myService.findAll().size();
   Assertions.assertEquals(0, size);
  }
}

这应该行得通

我相信您希望编写一个集成测试。在这里,您可以删除MockBean注释,只需自动连接存储库。此外,与SpringRunner课程一起跑步

@RunWith(SpringRunner.class)
public class MyTest {

  @Autowired
  MyRepository myRepository;

  @Autowired
  MyService myService
  
  @Test
  void testMy() {
   int size = myService.findAll().size();
   Assertions.assertEquals(0, size);
  }
}

这应该行得通

这里的问题是您的服务实现。当您运行整个应用程序时,使用
@Autowired
注入依赖项会起作用,但它不允许您在需要时注入自定义依赖项,测试就是一个很好的例子

将服务实现更改为:

@Service
public class MyService {

    private MyRepository myRepository;

    public MyService(MyRepository myRepository){
        this.myRepository = myRepository;
    }

    public List<MyEntity> findAll() {

        System.out.println(myRepository); // null
        return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
    }

    @Transactional
    public MyEntity create(MyEntity myEntity) {

        myRepository.save(myEntity);

        return myEntity;
    }
}
注意,我已将
@MockBean
替换为
@Mock
,因为前面的注释用于将模拟bean注入到spring上下文中,如果您正在进行单元测试,则不需要这样做。如果您想启动spring上下文(我不建议您这样做),您需要使用
@SpringBootTest
或其他一些可用的选项来配置测试类。这将把您的测试转换为集成测试


PD:如果不向
myRepository.findAll()
提供模拟,此测试将不起作用。Mockito的默认行为是返回null,但您希望它返回0,因此您需要执行类似于
given(myRepository.findAll())的操作。willReturn(0)
这里的问题是您的服务实现。当您运行整个应用程序时,使用
@Autowired
注入依赖项会起作用,但它不允许您在需要时注入自定义依赖项,测试就是一个很好的例子

将服务实现更改为:

@Service
public class MyService {

    private MyRepository myRepository;

    public MyService(MyRepository myRepository){
        this.myRepository = myRepository;
    }

    public List<MyEntity> findAll() {

        System.out.println(myRepository); // null
        return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
    }

    @Transactional
    public MyEntity create(MyEntity myEntity) {

        myRepository.save(myEntity);

        return myEntity;
    }
}
注意,我已将
@MockBean
替换为
@Mock
,因为前面的注释用于将模拟bean注入到spring上下文中,如果您正在进行单元测试,则不需要这样做。如果您想启动spring上下文(我不建议您这样做),您需要使用
@SpringBootTest
或其他一些可用的选项来配置测试类。这将把您的测试转换为集成测试

PD:如果不向
myRepository.findAll()
提供模拟,此测试将不起作用。Mockito的默认行为是返回null,但您希望它返回0,因此您需要执行类似于
given(myRepository.findAll())的操作。willReturn(0)

为什么呢?我是否缺少初始化存储库实例的单元测试类上的注释

基本上是的:)

您需要通过使用
@springbootest

另一个问题是手动创建
MyService
对象。 这样,SpringBoot就没有机会为您注入任何Bean。您可以通过简单地将
MyService
注入Testclass来解决这个问题。您的代码应该如下所示:

@SpringBootTest
public class MyTest {

    @Autowired
    private MyService myService;

    @Test
    void testMy() {
        int size = myService.findAll().size();
        assertEquals(0, size);
    }
}
为什么呢?我是否缺少初始化存储库实例的单元测试类上的注释

基本上是的:)

您需要通过使用
@springbootest

另一个问题是手动创建
MyService
对象。 这样,SpringBoot就没有机会为您注入任何Bean。您可以通过简单地将
MyService
注入Testclass来解决这个问题。您的代码应该如下所示:

@SpringBootTest
public class MyTest {

    @Autowired
    private MyService myService;

    @Test
    void testMy() {
        int size = myService.findAll().size();
        assertEquals(0, size);
    }
}

谢谢这里的信息。但我确实概述了我希望做一个集成样式的测试,这样我就不会模拟
myRepository.findAll()
的响应。我希望对内存数据库进行适当的查询,以返回0/null的结果。。因为我还没有在数据库中插入任何对象。谢谢这里的信息。但我确实概述了我希望做一个集成样式的测试,这样我就不会模拟
myRepository.findAll()
的响应。我希望对内存数据库进行适当的查询,以返回0/null的结果。。因为我还没有在数据库中插入任何对象。