NullpointerException: cannot get property on null object - spring

Working on IDEA and trying to launch the following code:
package com.myCompany.routing.spring
import com.dropbox.core.DbxRequestConfig
import grails.util.Holders
import spock.lang.Specification
class DropboxSpringConfigSpec extends Specification {
def grailsApplication=Holders.grailsApplication
def "It instantiates and configures the dropboxRequestConfig component"() {
given:
def ctx = grailsApplication.mainContext
//do stuff...
}
}
I get the following error:
java.lang.NullPointerException: Cannot get property 'mainContext' on null object
at com.myCompany.routing.spring.DropboxSpringConfigSpec.It instantiates and configures the dropboxRequestConfig component(DropboxSpringConfigSpec.groovy:20)
I've recently made a pull on my VCS, so the code should work.
When running the test as Grails test, I get the following error:
Error |
2015-03-04 13:32:00,989 [localhost-startStop-1] ERROR context.GrailsContextLoader - Error initializing the application:
Missing configuration in Config.groovy: connection.uri.

Okay, it seems some configurations in Config.groovy were given the values of some environment variables:
elasticSearch {
connection {
uri = env.ES_URL
username = env.ES_USER
password = env.ES_PASSWORD
}
indexPrefix = 'test-'
}
Since I never created the corresponding environment variables, the GrailsContextLoader fails to find the corresponding value and the computation fail.
Initializing the required environmnent variables in my IDE and running the tests as Grails tests solved the problem.

By your extends Specification seems that what you have there is a unit test. grailsApplication is not available on unit specs, though it can be mocked (ie. using the #TestFor(ClassUnderTest) annotation mockes it up for you).
If what you want to test is configuration I would recommend writing an integration spec. On the integration phase, you basically have a wired grails application without the web interface. In that case all you'll need would be to do is
package com.myCompany.routing.spring
import com.dropbox.core.DbxRequestConfig
import grails.util.Holders
import grails.test.spock.IntegrationSpec
class DropboxSpringConfigSpec extends IntegrationSpec {
def grailsApplication //This will be auto-wired
def "It instantiates and configures the dropboxRequestConfig component"() {
given:
def ctx = grailsApplication.mainContext
//do stuff...
}
}
Re that test having worked previously in your VCS. I've never used Holders on a unit spec, so I cannot really say it wouldn't work, but it may have just been a false positive. From my understanding on the unit phase you don't have a running grails application and beans (including config) would not be available until mocked by you or the testing framework (again, using #TestFor or #Mock)

Related

Is it posible to make spock specification conditional on property from Spring's application.properties?

Background:
project logic in Java 11 and Spring Boot 2.6.6
some project features are conditionally available depending on specific application properties, some Spring components related with conditional features are also dependent using #ConditionalOnProperty annotation on component
tests (also integration) are written in groovy and spock framework (ver. 2.1-groovy-3.0)
Question:
Is it posible to make spock specification conditional on property from spring's application.properties?
Spock framework provides annotations which make test conditional.
Most accurate seems to be #Requires for my case.
(https://spockframework.org/spock/docs/2.1/all_in_one.html#_requires)
Condition is based on PreconditionContext (https://spockframework.org/spock/docs/2.1/all_in_one.html#precondition_context).
Simplified Specificatiotion example (two working #Requires annotations left as example, but they do not check what is needed in my case):
import org.spockframework.runtime.extension.builtin.PreconditionContext
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import spock.lang.Requires
import spock.lang.Specification
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles('integration')
#ContextConfiguration(classes = TestSpringBootApplication)
//TODO: How to make this feature dependent of property from application.properties?
//#Requires(reason = 'Specification for AAA feature enabled', value = { isFeatureAAAEnabled() })
//#Requires(reason = 'Test run only on Linux', value = { PreconditionContext preconditionContext -> preconditionContext.os.windows })
class ConditionalSpec extends Specification {
//Some conditional components #Autowired
//feature methods
def "one plus one should equal two"() {
expect:
1 + 1 == 2
}
private static boolean isFeatureAAAEnabled() {
true
}
}
What do you want exactly, is it enough to just not run any tests but still start the spring context, or do you want to also avoid starting the spring context?
If it is the first one, then you can use instance or shared from the Precondition Context. If you enable shared field injection you should be able to do this.
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles('integration')
#ContextConfiguration(classes = TestSpringBootApplication)
#EnableSharedInjection
#Requires(reason = 'Specification for AAA feature enabled', value = { shared.myValue == 'featureAAA' })
class ConditionalSpec extends Specification {
#Value('${value.from.file}')
#Shared
String myValue
//feature methods
def "one plus one should equal two"() {
expect:
1 + 1 == 2
}
}
If you can't use shared injection due to it's limitations, then you'll have to replace shared by instance in the condition.
If you want to avoid starting spring, then you'll have to write your own extension to figure out what the value from the application.properties, and skip the spec yourself.

Cant inject Spring dependencies to both Spock and Spring at same time

I'm having troubles with setting up my tests. I'm using latest version of SpringBoot and Spock Framework. First of all, I'm not configuring my beans "traditional" way. All my classes in package except Facade are package-scoped. I'm not using #Component, #Service, etc.
The only one class Im injecting is Repository. Let me show you my Configuration class
#Configuration
class SurveyConfiguration {
#Bean
SurveyFacade surveyFacade(SurveyRepository surveyRepository) {
ConversionUtils conversionUtils = new ConversionUtils();
SurveyValidator surveyValidator = new SurveyValidator();
SurveyCreator surveyCreator = new SurveyCreator(surveyRepository, conversionUtils, surveyValidator);
return new SurveyFacade(surveyCreator);
}
}
It works fine, I'v tested all scenarios manually (sending POST to certain endpoint). Let me show you example method from SurveyCreator class I want to test.
SurveyDTO createSurvey(final SurveyDTO surveyDTO) throws ValidationException, PersistenceException {
Survey survey = conversionUtils.surveyToEntity(surveyDTO);
surveyValidator.validate(survey);
Optional<Survey> savedInstance = Optional.ofNullable(surveyRepository.save(survey)); //Will throw NullPtr
return savedInstance.map(conversionUtils::surveyToDTO)
.orElseThrow(PersistenceException::new);
}
Like I said, during runtime it works fine. So lets move on to tests
#SpringBootTest
class SurveyFacadeTest extends Specification {
#Autowired
private SurveyRepository surveyRepository
private SurveyFacade surveyFacade = new SurveyConfiguration().surveyFacade(this.surveyRepository)
def "should inject beans"() {
expect:
surveyRepository != null
surveyFacade != null
}
def "should create survey and return id"() {
given:
Long id
when:
id = surveyFacade.createSurvey(SampleSurveys.validSurvey())
then:
id != surveyFacade
}
}
First test passes, so I understand I got everything ok for tests. But, I'm getting NullPointer in my java code in method I posted above. Looks like SurveyRepository isnt injected into java code during tests, because thats the one that causes this exception... Any ideas how to work around that, to have my Repository injected in both Spring application and Spock tests?
If there are no reasons against, I recommend you to run the test on the "underlying bean" (and not a manually created instance):
#Autowired
private SurveyFacade surveyFacade;

Test service from with dependencies in spock

I am working with a kotlin and spring project, Now I am trying to do the test of some service, which has some dependencies, I am having some problems, in order to get a success test. Maybe I my design is not good enough, moreover I have problems trying to call the method from the spy object, I am getting the issue: Cannot invoke real method 'getClubhouseFor' on interface based mock object. This is my code, Could you give me any idea about what I am doing bad.
Thanks in advance!!!!
This is my code:
import com.espn.csemobile.espnapp.models.UID
import com.espn.csemobile.espnapp.models.clubhouse.*
import com.espn.csemobile.espnapp.services.clubhouse.AutomatedClubhouseService
import com.espn.csemobile.espnapp.services.clubhouse.ClubhouseService
import com.espn.csemobile.espnapp.services.clubhouse.StaticClubhouseService
import com.espn.csemobile.espnapp.services.clubhouse.contexts.ClubhouseContext
import com.espn.csemobile.espnapp.services.core.CoreService
import rx.Single
import spock.lang.Specification
class ClubhouseServiceImplTest extends Specification {
StaticClubhouseService staticClubhouseService = GroovyStub()
AutomatedClubhouseService automatedClubhouseService = GroovyStub()
CoreService coreService = GroovyStub()
ClubhouseContext clubhouseContext = GroovyMock()
Clubhouse clubHouse
ClubhouseLogo clubhouseLogo
ClubhouseService spy = GroovySpy(ClubhouseService)
void setup() {
clubhouseLogo = new ClubhouseLogo("http://www.google.com", true)
clubHouse = new Clubhouse(new UID(), "summaryType", ClubhouseType.League, new ClubhouseLayout(), "summaryName", "MLB", clubhouseLogo, "http://www.google.com", "liveSportProp",new ArrayList<Integer>(), new ArrayList<ClubhouseSection>(),new ArrayList<ClubhouseAction>(), new HashMap<String, String>())
}
def "GetClubhouseFor"() {
given:
staticClubhouseService.getClubhouseFor(clubhouseContext) >> buildClubHouseMockService()
// The idea here is to get different responses it depends on the class of call.
automatedClubhouseService.getClubhouseFor(clubhouseContext ) >> buildClubHouseMockService()
spy.getClubhouseFor(clubhouseContext) >> spy.getClubhouseFor(clubhouseContext)
when:
def actual = spy.getClubhouseFor(clubhouseContext)
then:
actual != null
}
def buildClubHouseMockService(){
return Single.just(clubHouse)
}
}
The next are the classes involved in the test:
import com.espn.csemobile.espnapp.models.clubhouse.*
import com.espn.csemobile.espnapp.services.clubhouse.contexts.ClubhouseContext
import com.espn.csemobile.espnapp.services.core.CoreService
import org.springframework.context.annotation.Primary
import org.springframework.context.annotation.ScopedProxyMode
import org.springframework.stereotype.Service
import org.springframework.web.context.annotation.RequestScope
import rx.Single
interface ClubhouseService {
fun getClubhouseFor(context: ClubhouseContext): Single<Clubhouse?>
}
#Service
#RequestScope(proxyMode = ScopedProxyMode.NO)
#Primary
class ClubhouseServiceImpl(private val clubhouseContext: ClubhouseContext,
private var staticClubhouseService: StaticClubhouseService,
private var automatedClubhouseService: AutomatedClubhouseService,
private val coreService: CoreService?): ClubhouseService {
override fun getClubhouseFor(context: ClubhouseContext): Single<Clubhouse?> {
return staticClubhouseService.getClubhouseFor(clubhouseContext).flatMap { clubhouse ->
if (clubhouse != null) return#flatMap Single.just(clubhouse)
return#flatMap automatedClubhouseService.getClubhouseFor(clubhouseContext)
}
}
}
Well, first of all GroovySpy or GroovyStub do not make sense for Java or Kotlin classes because the special features of Groovy mocks are only available for Groovy classes. So don't expect to be able to mock constructors or static methods that way, if that was the reason for the usage. This is also documented here:
When Should Groovy Mocks be Favored over Regular Mocks? Groovy mocks should be used when the code under specification is written in Groovy and some of the unique Groovy mock features are needed. When called from Java code, Groovy mocks will behave like regular mocks. Note that it isn’t necessary to use a Groovy mock merely because the code under specification and/or mocked type is written in Groovy. Unless you have a concrete reason to use a Groovy mock, prefer a regular mock.
As for your problem with the spy, you cannot use a spy on an interface type. This is documented here:
A spy is always based on a real object. Hence you must provide a class type rather than an interface type, along with any constructor arguments for the type.
So either you just switch to Mock or Stub, both of which work on interface types, or you spy on the implementation class instead. In any case, my main suggestion is to read the documentation first and then try to use a new tool like Spock. My impression is that you have not used Spock before, but of course I could be wrong.

How can I inject config properties into a unit test, using SpringBoot2, JUnit5, and Kotlin

My scenario:
I'm building an app that uses Kotlin and SpringBoot 2.0.3. I'm trying to write all my unit tests in JUnit5. All 3 of these are new to me, so I'm struggling a bit.
I'm using a #ConfigurationProperties class (instead of #Value) to inject values from my application.yml into my Spring context.
#Configuration
#ConfigurationProperties(prefix = "amazon.aws.s3")
class AmazonS3Config {
val s3Enabled: Boolean = false
val region: String = ""
val accessKeyId: String = ""
val secretAccessKey: String = ""
val bucketName: String = ""
}
I then have a Kotlin class that is utilizing these properties, following Kotlin/Spring best practice to define the injected class as a constructor parameter.
class VqsS3FileReader(val amazonS3Config: AmazonS3Config) : VqsFileReader {
companion object: mu.KLogging()
override fun getInputStream(filePath: String): InputStream {
val region: String = amazonS3Config.region
val accessKeyId: String = amazonS3Config.accessKeyId
val secretAccessKey: String = amazonS3Config.secretAccessKey
val bucketName: String = amazonS3Config.bucketName
logger.debug { "The configured s3Enabled is: $s3Enabled" }
logger.debug { "The configured region is: $region" }
logger.debug { "The configured accessKeyId is: $accessKeyId" }
logger.debug { "The configured secretAccessKey is: $secretAccessKey" }
logger.debug { "The configured bucketName is: $bucketName" }
val file: File? = File(filePath)
//This method is not yet implemented, just read a file from local disk for now
return file?.inputStream() ?: throw FileNotFoundException("File at $filePath is null")
}
}
I have not completed this implementation, as I'm trying to get the unit test working first. So for the moment, this method doesn't actually reach out to S3, just streams a local file.
My unit test is where I'm getting stuck. I don't know how to inject the properties from my application.yml into the test context. Since the ConfigProperty class is passed as a construction parameter, I have to pass it when I establish my service in my unit test. I've tried various solutions that don't work. I found this piece of info, which was helpful:
If Spring Boot is being used, then #ConfigurationProperties instead of #Value annotations can be used, but currently this only works with lateinit or nullable var properties (the former is recommended) since immutable classes initialized by constructors are not yet supported.
So this means I cannot use class VqsS3FileReaderTest(amazonS3Config: AmazonS3Config): TestBase() { ... } and then pass the config to my service.
This is what I have currently:
#ActiveProfiles("test")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ExtendWith(SpringExtension::class)
#ContextConfiguration(classes = [AmazonS3Config::class, VqsS3FileReader::class])
class VqsS3FileReaderTest(): TestBase() {
#Autowired
private lateinit var amazonS3Config: AmazonS3Config
#Autowired
private lateinit var fileReader: VqsS3FileReader
val filePath: String = "/fileio/sampleLocalFile.txt"
#Test
fun `can get input stream from a valid file path` () {
fileReader = VqsS3FileReader(amazonS3Config)
val sampleLocalFile: File? = getFile(filePath) //getFile is defined in the TestBase class, it just gets a file in my "resources" dir
if (sampleLocalFile != null) {
val inStream: InputStream = fileReader.getInputStream(sampleLocalFile.absolutePath)
val content: String = inStream.readBytes().toString(Charset.defaultCharset())
assert.that(content, startsWith("Lorem Ipsum"))
} else {
fail { "The file at $filePath was not found." }
}
}
}
With this, my test runs, and my context seems to setup properly, but the properties from my application.yml are not being injected. For my debug output, I see the following:
08:46:43.111 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured s3Enabled is: false
08:46:43.111 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured region is:
08:46:43.112 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured accessKeyId is:
08:46:43.112 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured secretAccessKey is:
08:46:43.112 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured bucketName is:
All empty strings, which is the default values. Not the values I have in my application.yml:
amazon.aws.s3:
s3Enabled: true
region: us-west-2
accessKeyId: unknown-at-this-time
secretAccessKey: unknown-at-this-time
bucketName: test-bucket
I see mistake in the following line:
#ContextConfiguration(classes = [AmazonS3Config::class, VqsS3FileReader::class])
Please put configuration classes here (instead of just beans).
Short - hot to fix test
Create class (if missing) like VqsS3Configration in the main module (e.g. in the module, where you have production code)
Create class like VqsS3TestConfigration in the same package with your tests. Content on this file:
#org.springframework.context.annotation.Configuration // mark, that this is configuration class
#org.springframework.context.annotation.Import(VqsS3Configration::class) // it references production configuration from test configuration
#org.springframework.context.annotation.ComponentScan // ask Spring to autoload all files from the package with VqsS3TestConfigration and all child packages
class VqsS3TestConfigration {
/*put test-related beans here in future*/
}
Then go to test and change declaration:
#ContextConfiguration(classes = [VqsS3TestConfigration ::class]) // we ask Spring to load configuration here
I created sample application here: https://github.com/imanushin/spring-boot2-junit5-and-kotlin-integration
Please execude line .\gradlew.bat test or gradlew.bat bootRun in the src folder. Test will check, that we able to read properties. bootRun will print auto-loaded properties
Boring theory
First of all - Spring has Configuration classes - they are needed to load and initialize other classes. Instead of Service or Comonent classes, main purpose of Configuration classes - just create services, components, etc.
If we will simplify algorithm of the Spring application load, then it will be like this:
Find Configuration classes
Read annotation of them, understand list of classes (e.g. reference tree), which should be loaded (and in addition - how they should be loaded)
Load classes with different ways:
3.1. For classes, which are annotated with #ConfigurationProperties - put configuration items here
3.2. For classes, which are annotated with #RestController - register them as rest controllers
3.N. etc...
How does Spring understand, what configuration should be loaded?
Formally is it done by Spring Boot, however I will name it as Spring
Understand several initial configurations - they can be put into the class SpringApplicationBuilder, into the test annotations (see above), into the XML context, etc. For our case we use test annotation and #ContextConfiguration attribute
Recursive get all imported configuration (e.g. Spring reads #Import annotation, then it get children, then it check their imports, etc.)
Use Spring Factories to get configuration automatically from jar
Therefore, in our case, Spring will do actions like this:
Get configuration from test annotation
Get all other configurations by recursive way
Load all classes into the contet
Start test
Okay, it took me all day, but I finally got my application properties to load into my unit test context. I made 2 changes:
First, I added the #Service annotation to my VqsS3FileReader service - which I had originally forgotten. Also, while I had updated my Test to not inject the AmazonS3Config via the constructor, I had neglected to update my service to do the same. So I changed
this:
class VqsS3FileReader(val amazonS3Config: AmazonS3Config) : VqsFileReader {
companion object: mu.KLogging()
...
to this:
#Service
class VqsS3FileReader : VqsFileReader {
companion object: mu.KLogging()
#Resource
private lateinit var amazonS3Config: AmazonS3Config
...
Finally, I modified my Spring annotations on my test.
from this:
#ActiveProfiles("test")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ExtendWith(SpringExtension::class)
#ContextConfiguration(classes = [AmazonS3Config::class, VqsS3FileReader::class])
class VqsS3FileReaderTest(): TestBase() {
...
to this:
#ActiveProfiles("test")
#SpringBootTest
#ComponentScan("com.ilmn.*")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ExtendWith(SpringExtension::class)
#EnableAutoConfiguration
#SpringJUnitConfig(SpringBootContextLoader::class)
class VqsS3FileReaderTest(): TestBase() {
...
It seems like I have an unordinary amount of annotations on my test now... so I will be looking carefully at what each of them really do, and see if I can reduce it. But at least my properties are being injected into my test context now.

how to pre-populate spring properties from tests

I've a slight race condition when it comes to loading spring properties for an integration test using #TestPropertySource.
Consider the following;
test (using Spock but same for JUnit)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#TestPropertySource(locations = "classpath:test/simple-test.properties")
class SimpleStuff extends Specification {
public static final String inputDirectoryLocation = "/tmp/input-test-folder"
def "test method"() {
//do test stuff
}
}
simple-test.properties
inputDirectoryLocation=/tmp/input-test-folder
Spring Component
#Component
class SpringComponent {
#Value('${inputDirectoryLocation}')
String inputDirectory;
//do other stuff
}
The above works fine but how would I make the test fully isolated and NOT have a dependency on the FileSystem having the folder /tmp/input-test-folder (as not all users running this test are allowed to create a /tmp folder on their FS)
For example, I would like to use something like
inputDirectoryLocation = Files.createTempDirectory()
so that
#Value('${inputDirectoryLocation}')
String inputDirectory;//equals the output of Files.createTempDirectory()
resulting in test using the OS default temporary folder location & allows us to have the test simply delete the temp folder on cleanup. Is there an eloquent solution to solve the above?
Note: using Spring boot 1.5
Turned out simple enough - simply had to change the value in the properties file to refer to the
inputDirectoryLocation=${java.io.tmpdir}/input-test-folder
Then have my Spock specification create the temp folder prior to launching Spring (by using the setup() fixture method )

Resources