【问题标题】:Test Service with Mockito, when Id is set by database当 Id 由数据库设置时,使用 Mockito 测试服务
【发布时间】:2019-07-16 16:36:20
【问题描述】:

我的服务休息服务中有一个函数 createObject():

@Service
public class MyService {

    //Repos and contructor

   @Transactional
   public ObjectDto createObject(Object) {

       Mother mother = new Mother(name, age);
       Kid kid = new Kid(name, age);

       mother.addKid(kid);

       this.motherRepo.saveAndFlush(mother); 

       Long kidId = kid.getId();

       doStuffWithKidId();

       return new ObjectDto()
            .withMother(mother)
            .withKid(kid)
            .build();
  }
}

我的母亲/孩子的实体基本上是这样的:

@Entity
@Table("mother")
public class mother() {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name="id)
   private Long id;

   //other attributes, including @OneToMany for Kid
   //Getter/Setter

}

Kid 有一个类似的实体。

如您所见,id 是由数据库设置的。实体中没有 id 的设置器。构造函数也没有id。

现在我想测试我的服务。我模拟了我的存储库并想验证我的 ObjectDto 是否包含值,例如 id。

@RunWith(MockitoJUnitRunner.class)
@SpringBootTest
public MyServiceTest {

    @Mock
    private MotherRepo motherRepo;

    @InjectMocks
    private MyService myService;

    @Test
    void createObjectTest() {

        ObjectDto expectedObjectDto = setup...;
        Object inputObject = setup...;

        assertThat.(this.myService.createObject(inputObject))
             .isEqualToComparingFieldByField(expectedObjectDto);

    }
}

预期的 ObjectDto 看起来像

{
   "motherId":1,
   "motherAge":40,
   "kidId":1
   ...
}

问题是,id 是由数据库设置的。由于没有数据库并且存储库是使用 Mockito 模拟的,因此该值始终为空。即使我将我的 expectedObjectDto 设置为 null 作为 id,我也需要服务中“doStuffWithKidId()”中的 id。 Atm 我得到一个 NullPointerException。

是否可以像 ReflectionTestUtils.setField() 那样设置 id?在我读到的文献中,应该始终使用模拟来测试服务。这是正确的还是我需要像 H2 这样的内存数据库?

感谢您的帮助。

【问题讨论】:

    标签: java spring-boot testing junit mockito


    【解决方案1】:

    使用doAnswer...

    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.junit.MockitoJUnitRunner;
    import org.springframework.test.util.ReflectionTestUtils;
    
    import static org.assertj.core.api.Java6Assertions.assertThat;
    import static org.mockito.Mockito.doAnswer;
    import static org.mockito.Mockito.mock;
    
    @RunWith(MockitoJUnitRunner.class)
    public class MockitoSettingDatabaseIds {
    
        private static class TestEntity {
            private long id;
            private String text;
    
            public TestEntity(String text) {
                this.text = text;
            }
    
            public long getId() {
                return id;
            }
    
            public String getText() {
                return text;
            }
        }
    
        private interface TestEntityDAO {
            void save(TestEntity entity);
        }
    
        private static long someLogicToTest(TestEntityDAO dao, TestEntity entity) {
            dao.save(entity);
            return entity.getId();
        }
    
        @Test
        public void shouldReturnDatabaseGeneratedId() {
            long expectedId = 12345L;
    
            TestEntityDAO dao = mock(TestEntityDAO.class);
            TestEntity entity = new TestEntity("[MESSAGE]");
    
            doAnswer(invocation -> {
                ReflectionTestUtils.setField((TestEntity) invocation.getArgument(0), "id", expectedId);
                return null;
            }).when(dao).save(entity);
    
            assertThat(someLogicToTest(dao, entity)).isEqualTo(expectedId);
        }
    }
    

    要回答您的评论,只需对 Kid 集合执行相同操作即可,例如...

            doAnswer(invocation -> {
                Mother toSave = (Mother) invocation.getArgument(0);
                ReflectionTestUtils.setField(toSave, "id", expectedId);
    
                for (int k = 0; k < toSave.getKids().size(); k++) {
                    ReflectionTestUtils.setField(toSave.getKids().get(k), "id", expectedId + k + 1);
                }
    
                return null;
            }).when(dao).save(entity);
    

    这会将Motherid 设置为expectedId,并将Kids 的ID 设置为expectedId + 1expectedId + 2 等。

    【讨论】:

    • 它有效,谢谢。我只是想把它调整为 doAnswer(...).when(doa).save(any()),因为我不能把它交给 someLogicToTest()。
    • 它适用于母实体。但是我无法为孩子实体使用该解决方案,因为我没有使用它的 doa.save() 或类似的东西。有什么建议吗?
    • 谢谢伙计,它有效!如果可以的话,我会多次投票 :) 非常感谢,我已经为此苦苦挣扎了 2 天。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多