嘲笑存储库函数导致空指针异常,尽管使用了when和thenReturn。

huangapple go评论79阅读模式
英文:

Mocking repository function leads to null pointer exception despite using when and thenReturn

问题

我的ServiceImpl看起来是这样的:

@Service
public class GuildUsersServiceImpl implements GuildUsersService {

  @Autowired
  private final GuildUsersRepository guildUsersRepository;

  @Autowired
  private GuildService guildService;

  @Autowired
  private UserService userService;

  @Autowired
  private GuildUserRoleTypeService guildUserRoleTypeService;

  @Autowired
  public GuildUsersServiceImpl(final GuildUsersRepository guildUsersRepository) {
    this.guildUsersRepository = guildUsersRepository;
  }

  public GuildUsers create(GuildUsers guildUser) {
    return this.guildUsersRepository.save(guildUser);
  }
}

而我的create方法的服务测试看起来是这样的:

@RunWith(SpringRunner.class)
public class GuildUsersServiceTest {
  
  @Mock private GuildUsersRepository guildUsersRepository;
  
  @Mock private UserService userService;

  @Mock private GuildService guildService;
  
  @Mock private GuildUserRoleTypeService guildUserRoleTypeService;

  @InjectMocks private GuildUsersServiceImpl guildUsersService;
  
  @Before
  public void setUp(){
      MockitoAnnotations.initMocks(this);
  }
  
  @Test
  public void createTest() {
    GuildUsers guildUsers = mockGuildUsers();
    when(guildUsersRepository.save(guildUsers)).thenReturn(guildUsers);
    GuildUsers dbGuildUsers = guildUsersService.create(guildUsers);
    assertEquals(dbGuildUsers.getId(),guildUsers.getId());
  }
}

我正在使用jUnit4。尽管在测试中使用了when(guildUsersRepository.save(guildUsers)).thenReturn(guildUsers);,但我遇到了一个空指针异常,错误跟踪如下:

java.lang.NullPointerException: Cannot invoke "com.project.entities.domain.GuildUsers.getId()" because "dbGuildUsers" is null

我怀疑在如何模拟@Autowired类方面出现了问题。在测试类中模拟类时,需要做出哪些不同的操作?

同时,我希望以下测试用例中的服务也能正常工作:

@Test
public void createTest1() {
  GuildUsers guildUsers = mockGuildUsers();
  GuildUsersWithoutGuildIdRequestDTO guildUsersWithoutGuildIdRequestDTO = new GuildUsersWithoutGuildIdRequestDTO();
  guildUsersWithoutGuildIdRequestDTO.setUserId(guildUsers.getUser().getId());
  guildUsersWithoutGuildIdRequestDTO.setGuildUserRoleTypeId(guildUsers.getGuildUserRoleType().getId());

  when(guildService.get(guildUsers.getGuild().getId())).thenReturn(Optional.of(guildUsers.getGuild()));
  when(guildRepository.findById(any())).thenReturn(Optional.of(guildUsers.getGuild()));
  when(userService.get(guildUsers.getUser().getId())).thenReturn(Optional.of(guildUsers.getUser()));
  when(guildUsersRepository.findById(guildUsers.getId())).thenReturn(null);
  when(guildUserRoleTypeService.get(guildUsers.getGuildUserRoleType().getId())).thenReturn(Optional.of(guildUsers.getGuildUserRoleType()));
  when(guildUsersRepository.save(any())).thenReturn(guildUsers);

  GuildUsersResponseDTO dbGuildUsersResponseDTO = 
      guildUsersService.create(guildUsers.getGuild().getId(), guildUsersWithoutGuildIdRequestDTO);
  assertEquals(dbGuildUsersResponseDTO.getGuildUserRoleType(), guildUsers.getGuildUserRoleType());
}
英文:

My ServiceImpl looks like this:

@Service
public class GuildUsersServiceImpl implements GuildUsersService {

  @Autowired
  private final GuildUsersRepository guildUsersRepository;

  @Autowired
  private GuildService guildService;

  @Autowired
  private UserService userService;

  @Autowired
  private GuildUserRoleTypeService guildUserRoleTypeService;

  @Autowired
  public GuildUsersServiceImpl(final GuildUsersRepository guildUsersRepository) {
    this.guildUsersRepository = guildUsersRepository;
  }

  public GuildUsers create(GuildUsers guildUser) {
    return this.guildUsersRepository.save(guildUser);
  }
}

And my service test for create looks like this:

@RunWith(SpringRunner.class)
public class GuildUsersServiceTest {
  
  @Mock private GuildUsersRepository guildUsersRepository;
  
  @Mock private UserService userService;

  @Mock private GuildService guildService;
  
  @Mock private GuildUserRoleTypeService guildUserRoleTypeService;

  @InjectMocks private GuildUsersServiceImpl guildUsersService;
  
  @Before
  public void setUp(){
      MockitoAnnotations.initMocks(this);
  }
  
  @Test
  public void createTest() {
    GuildUsers guildUsers = mockGuildUsers();
    when(guildUsersRepository.save(guildUsers)).thenReturn(guildUsers);
    GuildUsers dbGuildUsers = guildUsersService.create(guildUsers);
    assertEquals(dbGuildUsers.getId(),guildUsers.getId());
  }
}

I am using jUnit4. Despite using when(guildUsersRepository.save(guildUsers)).thenReturn(guildUsers); in the test, I run into a Null Pointer Exception with the following trace:

java.lang.NullPointerException: Cannot invoke "com.project.entities.domain.GuildUsers.getId()" because "dbGuildUsers" is null

I suspect something is wrong with how I have mocked the @Autowired classes. What needs to be done differently while mocking classes in the Test class ?

At the same time I want the services in the following test case to work as well :

@Test
  public void createTest1() {
    GuildUsers guildUsers = mockGuildUsers();
    GuildUsersWithoutGuildIdRequestDTO guildUsersWithoutGuildIdRequestDTO = new  GuildUsersWithoutGuildIdRequestDTO();
    guildUsersWithoutGuildIdRequestDTO.setUserId(guildUsers.getUser().getId());
    guildUsersWithoutGuildIdRequestDTO.setGuildUserRoleTypeId(guildUsers.getGuildUserRoleType().getId());
    
    when(guildService.get(guildUsers.getGuild().getId())).thenReturn(Optional.of(guildUsers.getGuild()));
    when(guildRepository.findById(any())).thenReturn(Optional.of(guildUsers.getGuild()));
    when(userService.get(guildUsers.getUser().getId())).thenReturn(Optional.of(guildUsers.getUser()));
    when(guildUsersRepository.findById(guildUsers.getId())).thenReturn(null);
    when(guildUserRoleTypeService.get(guildUsers.getGuildUserRoleType().getId())).thenReturn(Optional.of(guildUsers.getGuildUserRoleType()));
    when(guildUsersRepository.save(any())).thenReturn(guildUsers);
    
    GuildUsersResponseDTO dbGuildUsersResponseDTO = 
        guildUsersService.create(guildUsers.getGuild().getId(),guildUsersWithoutGuildIdRequestDTO);
    assertEquals(dbGuildUsersResponseDTO.getGuildUserRoleType(),guildUsers.getGuildUserRoleType());
  }

答案1

得分: 2

我不知道为什么您同时进行了字段注入和构造函数注入。您应该首先删除其中一个,例如:

@Service
public class GuildUsersServiceImpl implements GuildUsersService {
  
  //@Autowired - 已删除
  private final GuildUsersRepository guildUsersRepository;

  @Autowired
  private GuildService guildService;

  @Autowired
  private UserService userService;

  @Autowired
  private GuildUserRoleTypeService guildUserRoleTypeService;

  //@Autowired - 已删除
  public GuildUsersServiceImpl(final GuildUsersRepository guildUsersRepository) {
    this.guildUsersRepository = guildUsersRepository;
  }

  public GuildUsers create(GuildUsers guildUser) {
    return this.guildUsersRepository.save(guildUser);
  }
}

或者删除构造函数以添加:

  @Autowired 
  private final GuildUsersRepository guildUsersRepository;
英文:

I don't know why you did field injection as well as constructor injection at the same time. You should remove one of them first, for instance:

@Service
public class GuildUsersServiceImpl implements GuildUsersService {
  
  //@Autowired - removed
  private final GuildUsersRepository guildUsersRepository;

  @Autowired
  private GuildService guildService;

  @Autowired
  private UserService userService;

  @Autowired
  private GuildUserRoleTypeService guildUserRoleTypeService;

  //@Autowired - removed
  public GuildUsersServiceImpl(final GuildUsersRepository guildUsersRepository) {
    this.guildUsersRepository = guildUsersRepository;
  }

  public GuildUsers create(GuildUsers guildUser) {
    return this.guildUsersRepository.save(guildUser);
  }
}

OR remove the constructor to add:

  @Autowired 
  private final GuildUsersRepository guildUsersRepository;```

</details>



# 答案2
**得分**: 0

InjectMock文档明确表示:“Mockito只会尝试通过构造函数注入、setter注入或按顺序和如下所述的属性注入来注入模拟。”

在您的情况下,显然它选择了属性注入,因此guildUserRoleTypeService保持未初始化状态。正如上面指出的,混合使用属性注入和构造函数注入并不是一个好主意。

为了改进GuildUsersServiceImpl的总体设计和可测试性,我建议坚持使用构造函数注入。这样您可以:
a)将所有字段声明为private final,这样一旦对象创建,它们就会被设置,避免了竞态条件和空指针异常。
b)在测试中使用适当的构造函数初始化GuildUsersServiceImpl,而不是使用InjectMocks。

<details>
<summary>英文:</summary>

InjectMock documentation clearly says: &quot;Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection in order and as described below.&quot;

In your case, it apparently chooses property injection, hence guildUserRoleTypeService remains uninitialised. Mixing property and constructor injection is not a good idea as pointed above.

To improve the general design and testability of your GuildUsersServiceImpl, I would suggest to stick with constructor injection. This way you can:
a) Declare all the fields as private final, so they will always be set once the object is created, avoiding race conditions and NPEs.
b) Initialise GuildUsersServiceImpl in the test using a proper constructor rather than InjectMocks.



</details>



# 答案3
**得分**: 0

把这个留在这里,为了其他也在浪费一个下午尝试让存储库返回他们配置的内容的人。最终我需要做的是,与其用`@Mock`注解存储库,我必须使用`@MockBean`注解,以使Mockito/Spring组合正常工作。

根据`@MockBean`的javadoc:

&gt; 当在字段上使用@MockBean时,除了在应用程序上下文中注册外,该模拟还将被注入到该字段中。

<details>
<summary>英文:</summary>

Leaving this here for other people that also wasted an afternoon on trying to get a repository to return what they configured. What ended up being the thing I had to do is rather than annotating the repository with `@Mock` I had to use the `@MockBean` annotation for the Mockito/Spring combination to work properly.

As per javadoc of `@MockBean`:

&gt; When @MockBean is used on a field, as well as being registered in the application context, the mock will also be injected into the field.

</details>



huangapple
  • 本文由 发表于 2020年10月25日 05:01:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/64517961.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定