Search code examples
spockspring-test

spock testing of endpoint and repository


Working on my Spring 2.67 and Spock 2.1-groovy-3.0 testing. I have the basic testing working but now trying some integration testing without success. I have a controller with:

   private ApiService apiService;

   @Autowired
   public ApiController(ApiService apiService) {

        this.apiService = apiService;
    }

    @GetMapping("api/{scannedId}")
    @ResponseBody
    public ResponseEntity getScannedId(@PathVariable String scannedId) {

        try {
            logger.info("ApiKey Controller received GET /api/" + scannedId);
            ApiKey found = apiService.retrieveValidApiKey(scannedId);
            ...
          }
...

The apiService has :

 private ApiRepository apiRepository;

@Autowired
public ApiService(ApiRepository apiRepository) {
    this.apiRepository = apiRepository;
}

public ApiKey retrieveValidApiKey(String uuid) {

    ApiKey anApi = apiRepository.getApiKeyByApiKey(uuid);
    if (anApi == null) {
        logger.info("ApiService.retrieveValidApiKey({}) failed to find a matching ApiKey", uuid);
        return null;
    }

I have a Spock test that seeds the database with two values and then successfully calls the /api endpoint. I have code in the test that confirms the two values were inserted, but when the actual ApiService class is called, they are not found:

  @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase
class ApiControllerTest extends Specification {

    @Shared Logger logger = LoggerFactory.getLogger(this.getClass())

    @Autowired
    ApiController apiController

    @Autowired
    ApiRepository apiRepository

 @Transactional
 def "GET by scannedId using apikey #apiKey should be #resultCode"() {
        given:

        def foundList = apiRepository.findAll()
        logger.info("{} apiKeys in repository", foundList.size())

        for (int i = 0; i < foundList.size(); i++) {
            logger.info("Found ApiKey #{} apiKey: {} & uuid: {}", i, foundList.get(i).apiKey, foundList.get(i).uuid)
        }

        when:
        def foundListCount = apiRepository.getApiKeyByApiKey(apiKey)
        logger.info("FoundList: {}", foundListCount)
        ResponseEntity<ApiKey> result = restTemplate.getForEntity( "/api/{scannedId}", ApiKey.class, apiKeyValue1)
        logger.info("TestRestTemplate returned apikey: {}", result)

        then:
        assert result.getStatusCode() == resultCode

        where:
        apiKey       || resultCode
        "testApiKey3" || HttpStatus.NOT_FOUND
        apiKeyValue1 || HttpStatus.OK
        apiKeyValue2 || HttpStatus.OK
    }

def setup() {
        def apiKey1 = new ApiKey(apiKey: apiKeyValue1, uuid: uuid1, beginDate: beginDate1, endDate: endDate1)
        def apiKey2 = new ApiKey(apiKey: apiKeyValue2, uuid: uuid2, beginDate: beginDate2, endDate: endDate2)
        apiRepository.saveAndFlush(apiKey1)
        apiRepository.saveAndFlush(apiKey2)
    }

When I run the test, the logger in the test method spits out all the persisted values. But the test fails because the ApiService.getScannedId fails because it does not see the values persisted in test setup.

I cannot use the @DataJpaTest because the ApplicationContext isn't loaded then, so the endpoints fail.

I am not sure why Spock sees the values persisted via Repository, but the ApiService doesn't. Is it a context issue? I really would like to test without mocks here if at all possible.


Solution

  • The problem is that your test is annotated with @Transactional that means that only things that run in that method can see the data. The rest request you are sending out, will be handled by another thread that doesn't have access to the transaction and thus will not see the data.

    You'll have to remove the annotation if you want it to work, but then you'll also have to clean the inserted data manually at the end of the test/cleanup(), since you can't rely on the transaction rollback.