Chamila Wijayarathna
Chamila Wijayarathna

Reputation: 1933

Writing a test to Spring boot REST API that retrieve data from a DB

I have a spring boot REST API with a GET method that returns data available in a DB. I am attempting to write an integration test to test this API method. I have configured the test to use the H2 database. I am trying to add some mock data to the database before the test is executed and see if the API retrieves that data. Following is the code I have written so far.

@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());


    }
}

However, when I run this code, I am getting an error saying "org.springframework.dao.InvalidDataAccessApiUsageException: No transactional EntityManager available; nested exception is java.lang.IllegalStateException: No transactional EntityManager available"

I have also attempted using @MockBean instead of @Autowired to bind the provinceDAO. Even though this prevents the error, it does not persist the entity in the database.

How should I write my testcase to test my method here?

Update:

application-test.properties

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

Entity -> 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;
    }
}

Upvotes: 2

Views: 2646

Answers (2)

Chamila Wijayarathna
Chamila Wijayarathna

Reputation: 1933

I managed to feed the database with the required data by placing a SQL script data-h2.sql with insert queries at the test/resources folder. This prevented the requirement to use an EntityManager or a DAO.

Furthermore, I added the following property to the application-test.properties file.

spring.datasource.platform=h2

Upvotes: 1

abhinav kumar
abhinav kumar

Reputation: 1803

In Order to test Rest Api You can try functional test as well as integration test. You can prepare your own response formate as required and check whether the same is returned or else you can also verify whether the data from db is fine or not.Plz check the below example

@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);
   }
}

In First test case i am forming the response format and trying to return the same and then validating the same.Here i don't need the db data so i have kept service as mock instead of auto wired.And used ObjectMapper for converting json to java and then java obj to json from base Test class.

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);
   }
   }

In below case we are getting the actual data in json format as we are doing rest api call and then just validating the status apart from status you can also cross check the data

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 

   }

Upvotes: 0

Related Questions