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.
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.
Related
Somewhere in my shared library I got a helper class like this:
class Helper {
def script
Helper(script) {
this.script = script
}
void sendTemplate(String webhook, String template, Map<String, String> values, TemplateMapper mapper) {
def body = mapper.map(template, values)
def resp = script.httpRequest(contentType: 'APPLICATION_JSON', httpMode: 'POST',
requestBody: body, url: webhook)
if (resp.status != 200) {
throw new UnableToNotifyException()
}
}
}
I'm trying to test said class like so:
class HelperSpec extends JenkinsPipelineSpecification {
def helper
def setup() {
helper = new Helper(this)
}
def "a test"() {
setup:
def webhook = 'aWebhook'
def template = '%replaceMe'
def values = ['%replaceMe': 'hello world!']
def mapper = new SimpleTemplateMapper()
getPipelineMock('httpRequest')(_) >> [status: 200]
when:
helper.sendTemplate(webhook, template, values, mapper)
then:
1 * getPipelineMock('httpRequest')(_)
}
}
I'm using gradle and my build.gradle file has
testImplementation 'org.jenkins-ci.plugins:http_request:1.10#jar'
Other steps' tests run perfectly but with this one I always get
java.lang.IllegalStateException: There is no pipeline step mock for [httpRequest].
1. Is the name correct?
2. Does the pipeline step have a descriptor with that name?
3. Does that step come from a plugin? If so, is that plugin listed as a dependency in your pom.xml?
4. If not, you may need to call explicitlyMockPipelineStep('httpRequest') in your test's setup: block.
And when I use explicitlyMockPipelineStep('httpRequest') I get a null pointer exception, because, I presume, the default mock returns a null.
Is there anything I'm missing in the test to get it working? Thanks in advance!!!
I'm starting with unit testing in Grails 3.3.8, I have the ControllerSpec:
class FooControllerSpec extends Specification implements ControllerUnitTest<FooController> {
void "test index"() {
given:
controller.fooService = new FooServiceImplementation()
when:
controller.index()
then:
response.json
status == 200
}
void "test show"() {
given:
controller.fooService = new FooServiceImplementation()
when:
params['id'] = '000001400'
controller.show()
then:
status == 200
}
}
FooService is an interface, does not have implementation.
#Service(Foo)
interface FooService {
Foo get(Serializable id)
List<Foo> list(Map args)
}
I create a FooServiceImplementation like this:
class FooServiceImplementation implements FooService {
Foo get(Serializable id) {/*implementation here*/}
List<Foo> list(Map args) {/*implementation here*/}
}
This actually works, but there is another form to initialize the service in my controller?
I want to use the FooService exactly with the implementation that grails would compile when injecting the service into the controller, this to make sure that the results during the execution will be the same as during the testing, since my FooServiceImplementation could generate different results, although I suppose you should write the implementation correctly.
#SpringBootTest(webEnvironment = RANDOM_PORT)
#ActiveProfiles("test")
class ContextLoadingSpec extends Specification {
#Autowired
TestRestTemplate testRestTemplate
def '/ should load the context'() {
when:
ResponseEntity<Object> entity = testRestTemplate.getForEntity('/', Object.class)
then:
entity.statusCode == HttpStatus.OK
}
#TestConfiguration
static class Config {
#Bean
RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.basicAuthorization('user', 'xxxxxxxx')
}
}
}
Creating a TestConfiguration doesn't seem correct to me here. The credentials should be in 'application.yml' and the authentication call should have been mocked. Please suggest a better way of writing this test.
Service:
#GrailsCompileStatic
class MyService {
final static String PLACEHOLDER = '<x>'
#Value('${myService.url}') // Suppose it http://example.com/doc-<x>.html
private String urlTemplate
String getSomeUrl(String lang) {
urlTemplate.replace(PLACEHOLDER, lang)
}
}
Unit test:
#TestFor(MyService)
class MyServiceSpec extends Specification {
#Unroll
void "test get some url for #lang"() {
when:
def someUrl = service.getSomeUrl(lang) // NullPointerException, because urlTemplate is null
then:
someUrl.endsWith(lang + '.html')
where:
lang << ['es', 'en']
}
}
So, as I mentioned above, urlTemplate is null (but config value exists in .properties). How to fix it?
Solution:
class MyServiceSpec extends IntegrationSpec {
MyService myService
#Unroll
void "test get some url for #lang"() {
when:
def someUrl = myService.getSomeUrl(lang)
then:
someUrl.endsWith(lang + '.html')
where:
lang << ['es', 'en']
}
}
Unit tests are used to test isolated units of code. If you are testing behavior that is dependent on the configuration value, inject it into the unit test to achieve reusable unit tests.
On the other hand, if you are testing that the variable is actually set or what the variable is set to, you need to use an integration test because you are basically testing your integration with Grails' configuration mechanism: http://docs.grails.org/latest/guide/testing.html#integrationTesting
As a third option, you could also use functional testing to verify that in the end everything appears to function as it is supposed to: http://docs.grails.org/latest/guide/testing.html#functionalTesting
How to bind #Value annotated fields of service in unit tests?
One way to do it...
#TestFor(MyService)
class MyServiceSpec extends Specification {
#Unroll
void "test get some url for #lang"() {
when:
service.urlTemplate = 'some value'
def someUrl = service.getSomeUrl(lang)
then:
someUrl.endsWith(lang + '.html')
where:
lang << ['es', 'en']
}
}
In case this helps someone.
I had an issue where a missing property variable used in a #Value was giving me an BeanExpressionException for a service unit test. I found that setting that variable in my application.yml for the test environment solved my problem in Grails 4.
#Value in question:
#Value("#{\${some_property_here}}") public Map<String, Map<String, Integer>> SOME_MAP_OF_MAPS
I would like to mock a service method in an integration test for one test, however I don't know how to get a reference to the service as it's added to the controller via dependency injection. To further complicate things the service is in a webflow, but I know it's not stored in the flow as the service is not serialized.
Ideal mocking scenario:
Get reference to the service
Mock the method via the metaClass
Main test
Set the metaClass to null so it's replaced with the original
Methods like mockFor so far don't seem to effect the service.
Example of the setup:
Controller:
package is.webflow.bad
import is.webflow.bad.service.FakeService
class FakeController
{
def index = {
redirect(action: 'fake')
}
def fakeFlow = {
start {
action {
flow.result = fakeService.fakeCall()
test()
}
on('test').to('study')
}
study {
on('done').to('done')
}
done {
System.out.println('done')
}
}
}
Service:
package is.webflow.bad.service
class FakeService
{
def fakeCall()
{
return 'failure'
}
}
Test:
package is.webflow.bad
import static org.junit.Assert.*
import grails.test.WebFlowTestCase
import is.webflow.bad.service.FakeService
import org.junit.*
class FakeControllerFlowIntegrationTests extends WebFlowTestCase
{
def controller = new FakeController()
def getFlow() { controller.fakeFlow }
String getFlowId() { "fake" }
#Before
void setUp() {
// Setup logic here
super.setUp()
}
#Test
void testBasic()
{
startFlow()
assertCurrentStateEquals 'study'
assertEquals 'failure', getFlowScope().result
}
#Test
void testServiceMetaClassChange()
{
// want to modify the metaClass here to return success
startFlow()
assertCurrentStateEquals 'study'
assertEquals 'success', getFlowScope().result
}
}
You can inject the service into your Integration test using "#AutoWired" or using application context you get reference. Am i missing something?
#Autowired
private YourService yourservice;
or
#Autowired
private ApplicationContext appContext;
YourService yourService = (YourService)appContext.getBean("yourService");
Here you go:
void "test something"() {
given: "Mocked service"
someController.someInjectedService = [someMethod: { args ->
// Mocked code
return "some data"
] as SomeService
when: "Controller code is tested"
// test condition
then: "mocked service method will be called"
// assert
}