【问题标题】:Writing a test to Spring boot REST API that retrieve data from a DB向 Spring Boot REST API 编写测试以从数据库中检索数据
【发布时间】:2020-06-14 14:10:52
【问题描述】:

我有一个带有 GET 方法的 Spring Boot REST API,它返回数据库中可用的数据。我正在尝试编写一个集成测试来测试这个 API 方法。我已将测试配置为使用 H2 数据库。我正在尝试在执行测试之前向数据库添加一些模拟数据,并查看 API 是否检索到该数据。以下是我目前写的代码。

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(locations = "classpath:application-test.properties")
public class MetaControllerTest {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private ProvinceDAO provinceDAO;

    @Transactional
    @Before
    public void addData () {
        Province southern = getProvinceEntity("Southern", "දකුණ", "தென்");
        provinceDAO.createEntity(southern);
        System.out.println(provinceDAO.findAll(Province.class).size());
    }

    @Test
    public void testGetProvinces() throws Exception {

        MvcResult result = mvc.perform(get("/meta/provinces"))
                .andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                .andReturn();
        System.out.println(result.getResponse().getContentAsString());


    }
}

但是,当我运行此代码时,我收到一条错误消息“org.springframework.dao.InvalidDataAccessApiUsageException: No transactional EntityManager available; nested exception is java.lang.IllegalStateException: No transactional EntityManager available

我也尝试使用@MockBean 而不是@Autowired 来绑定provinceDAO。即使这样可以防止错误,它也不会将实体持久保存在数据库中。

我应该如何编写测试用例来测试我的方法?

更新:

application-test.properties

spring.datasource.url = jdbc:h2:mem:test
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect

实体 -> Province.java

@Entity
@Table(name = "w4a_province")
public class Province {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private int id;

    @Column(name = "province_name")
    private String name;

    @Column(name = "province_name_si")
    private String nameSi;

    @Column(name = "province_name_ta")
    private String nameTa;
    .
    .
}

GenericDAO.java

@Repository
public class GenericDAO<T> implements IGenericDAO<T> {

    @PersistenceContext
    private EntityManager em;


    @Override
    public Session getCurrentSession() {
        return this.em.unwrap(Session.class);
    }
    @Override
    public T findByPrimaryKey(Class<T> clazz, Object primaryKey) {
        return getCurrentSession().find(clazz, primaryKey);
    }


    @Override
    public List<T> findAll(Class<T> clazz) {
        DetachedCriteria criteria = DetachedCriteria.forClass(clazz);
        return criteria.getExecutableCriteria(getCurrentSession()).list();
    }


    @Override
    public T createEntity(T entity) {
        getCurrentSession().save(entity);
        return entity;
    }

ProvinceDAOImpl.java

@Repository
public class ProvinceDAOImpl  extends GenericDAO<Province> implements ProvinceDAO {
}

MetaController.java

@RestController
@PreAuthorize("permitAll()")
public class MetaController {

    private final MetaService metaService;


    @Autowired
    public MetaController(MetaService metService) {
        this.metaService = metService;
    }

    @GetMapping("/meta/provinces")
    public ResponseEntity<List<ProvinceDTO>> getProvinces() {
        if (logger.isDebugEnabled()) {
            logger.debug("Retrieving list of provinces.");
        }

        List<ProvinceDTO> provinces =  metaService.getProvinces();
        return ResponseEntity.ok(provinces);
    }
}

MetaServiceImpl.java

@Service
@Transactional
public class MetaServiceImpl implements MetaService {

    private final ProvinceDAO provinceDAO;
    @Autowired
    public MetaServiceImpl(ProvinceDAO provnceDAO) {
        this.provinceDAO = provnceDAO;
    }

    public List<ProvinceDTO> getProvinces() {
        if (logger.isDebugEnabled()) {
            logger.debug("Obtaining a list of provinces from database.");
        }
        List<Province> entities = provinceDAO.findAll(Province.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Converting province entities to dtos.");
        }
        List<ProvinceDTO> dtos = new ArrayList<>();
        for (int i = 0; i < entities.size(); i++) {
            Province entity = entities.get(i);
            if (LocaleContextHolder.getLocale().getLanguage().equals(
                    GlobalConstants.LanguageIdentifiers.SINHALA_LANGUAGE_TAG)) {
                dtos.add(new ProvinceDTO(entity.getId(), entity.getNameSi()));
            } else if (LocaleContextHolder.getLocale().getLanguage().equals(
                    GlobalConstants.LanguageIdentifiers.TAMIL_LANGUAGE_TAG)) {
                dtos.add(new ProvinceDTO(entity.getId(), entity.getNameTa()));
            } else {
                dtos.add(new ProvinceDTO(entity.getId(), entity.getName()));
            }
        }
        return dtos;
    }
}

【问题讨论】:

  • 能否请您添加MetaControllerProvinceDAO的代码以及application-test.properties的内容
  • 我用控制器、DAO 和实体代码更新了问题。
  • 您的应用程序在您启动它并使用 curl 或 Postman 手动访问端点时是否正常工作?你也可以分享MetaService好吗
  • 我已经添加了 MetaServiceImpl.java。我已经使用 AdvancedRestClient chrome 扩展测试了应用程序端点,它工作正常。

标签: spring spring-boot spring-mvc integration-testing spring-boot-test


【解决方案1】:

我设法通过在 test/resources 文件夹中放置一个带有插入查询的 SQL 脚本 data-h2.sql 来为数据库提供所需的数据。这阻止了使用 EntityManager 或 DAO 的要求。

此外,我在application-test.properties 文件中添加了以下属性。

spring.datasource.platform=h2

【讨论】:

    【解决方案2】:

    为了测试 Rest Api 您可以尝试功能测试以及集成测试。 您可以根据需要准备自己的响应格式并检查是否返回相同的格式,或者您还可以验证来自 db 的数据是否正常。请检查以下示例

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes = FactsMain.class)
    @WebAppConfiguration
    public abstract class BaseTest {
       protected MockMvc mvc;
       @Autowired
       WebApplicationContext webApplicationContext;
    
       protected void setUp() {
          mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
       }
       protected String mapToJson(Object obj) throws JsonProcessingException {
          ObjectMapper objectMapper = new ObjectMapper();
          return objectMapper.writeValueAsString(obj);
       }
       protected <T> T mapFromJson(String json, Class<T> clazz)
          throws JsonParseException, JsonMappingException, IOException {
    
          ObjectMapper objectMapper = new ObjectMapper();
          return objectMapper.readValue(json, clazz);
       }
    }
    

    在第一个测试用例中,我正在形成响应格式并尝试返回相同的格式,然后验证相同的格式。这里我不需要数据库数据,所以我将服务保持为模拟而不是自动连接。并使用 ObjectMapper将 json 转换为 java,然后将 java obj 从基本 Test 类转换为 json。

    public class PersonalDetailsControllerTest extends BaseTest {
    
        @MockBean
        private IPersonalService service;
    
        private static final String URI = "/api/personalDetails";
    
       @Override
       @Before
       public void setUp() {
          super.setUp();
       }
    
       @Test
       public void testGet() throws Exception {
          PersonalDetailsEntity entity = new PersonalDetailsEntity();
          List<PersonalDetailsEntity> dataList = new ArrayList<PersonalDetailsEntity>();
          FactsAdminResponse<PersonalDetailsEntity> dataResponse = new FactsAdminResponse<PersonalDetailsEntity>();
    
          entity.setId(1);
          entity.setName(“Anthony Holmes”);
          entity.setAge(26);
          entity.setCity(“Banglore”);
          entity.setCountry(“India”);
    
          dataList.add(entity);
    
          dataResponse.setData(dataList);
    
          Mockito.when(service.getBuildings()).thenReturn(dataList);
    
          RequestBuilder requestBuilder =  MockMvcRequestBuilders.get(URI)
                     .accept(MediaType.APPLICATION_JSON);
    
            MvcResult mvcResult = mvc.perform(requestBuilder).andReturn();
            MockHttpServletResponse response = mvcResult.getResponse();
    
            String expectedJson = this.mapToJson(dataResponse);
            String outputInJson = mvcResult.getResponse().getContentAsString();
    
            assertEquals(HttpStatus.OK.value(), response.getStatus());
            assertEquals(expectedJson, outputInJson);
       }
       }
    

    在下面的例子中,当我们进行rest api调用时,我们正在获取json格式的实际数据,然后除了验证状态之外,您还可以交叉检查数据

    public class PersonalDetailsControllerTest extends BaseTest {
    
        private static final String URI = "/api/personalDetails";
    
       @Override
       @Before
       public void setUp() {
          super.setUp();
       }
    
     @Test
       public void getGet() throws Exception {
    
          MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(URL)
             .accept(MediaType.APPLICATION_JSON_VALUE)).andReturn();
    
          int status = mvcResult.getResponse().getStatus();
          assertEquals(200, status);
          String content = mvcResult.getResponse().getContentAsString();
          //you got the content in string format now you can also validate the data 
    
       }
    

    【讨论】:

    • 我在这里遇到的问题是,如何将一些初始数据添加到数据库中,以便在调用该方法时验证我的 API get 方法是否返回了数据库中的实际数据。据我了解,在您的第二个示例中,您添加了一些数据,但您没有将它们保存在数据库中。
    • 在我的第二个示例中,我没有添加任何数据,我只是进行 api 调用并验证状态。如果要在 db 中添加数据,请先进行 post/save 调用
    • 我的 API 没有针对该特定资源的发布功能。它最初将通过在数据库上运行 sql 脚本来提供。我遇到的问题是如何将这些数据输入到测试数据库中
    • 如果您使用的是 jpa 或 hibernate,那么您可以使用所需数据在实体上调用 save 方法
    • 当我得到这个“没有可用的事务实体管理器”错误时
    猜你喜欢
    • 2020-08-07
    • 2015-07-24
    • 1970-01-01
    • 1970-01-01
    • 2016-07-09
    • 2021-09-08
    • 2023-03-25
    • 2015-03-21
    • 1970-01-01
    相关资源
    最近更新 更多