SpringBoot junit tests with multiple TomcatServletContainerInitializer 's - BindException: Address already in use" - spring-boot

I have spring boot app and currently all my test are running green if ai run them separately i.e. manually, but when i run the maven package command then all test run in a serial mode i.e. after each other. What happens then is that i get:
"java.net.BindException: Address already in use".
Basically because the test are running in serial they all try to claim the socket 127.0.0.1:8081. And since the the socket is in a
TIME_WAIT state the second test is not able to claim that socket. i.e. the output of netstat -apn
netstat -apn |grep -i 8080
tcp 0 0 127.0.0.1:8080 127.0.0.1:33952 TIME_WAIT
Now i have two different profiles configured i.e. Development and Production i.e. see below:
#Configuration #Profile("Development") public class
TomcatEmbededDevelopmentTesting1Profile extends
SpringServletContainerInitializer { ... }
#Configuration #Profile("Production") public class
TomcatEmbededProductionProfile extends
SpringServletContainerInitializer { .. }
What I am looking after now is a feature to specify which customer TomcatServletContainerInitializer i can run i.e. i would have 5 different one all listening on different ports/sockets.
The problem is if I have 5 different TomcatEmbeded configuration classes that are all marked with "#Profile("Development")" then how do i tell junit to execute the one that i need. I have tired using the #SpringApplicationConfiguration , but that does not fly.
#SpringApplicationConfiguration(classes = {
...
TomcatEmbededDevelopmentTesting1Profile.class,
...
} )
then i found an article on the net that explains that there is some magic annotation #IntegrationTest("server.port:0") which is suppose to randomize that port, but that did not worked for me as well.
What is the right way to do it in spring? Any hints are greatly appreciated.
here is example of my test:
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners(listeners={ ServletTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
WithSecurityContextTestExecutionListener.class
}
)
#SpringApplicationConfiguration(classes = {
SecurityWebApplicationInitializerDevelopment.class,
SecurityConfigDevelopment.class,
TomcatEmbededDevelopmentTesting1Profile.class,
Internationalization.class,
MVCConfigDevelopment.class,
PersistenceConfigDevelopment.class,
ServerAuthenticationSuccessHandler.class,
ServerRedirectAuthenticationSuccessHandler.class,
LogicServiceRegistryPostProcessor.class
} )
#WebAppConfiguration
#EnableWebSecurity
#EnableWebSocket
#EnableGlobalMethodSecurity(prePostEnabled = true)
//#IntegrationTest({"server.port=0", "management.port=0"})
#ActiveProfiles("Development")
public class quickTest extends TestCase {
..
}

For starters, #Enable* annotations are not supported on test classes with Spring Boot 1.3.x. So delete those and move them to actual #Configuration classes.
Secondly, supplying a huge list of classes to #SpringApplicationConfiguration is a worst practice. The best practice is to define a single #Configuration class for your integration tests which uses #Import to include whatever other configuration classes you need.
Third, use #WebIntegrationTest(randomPort = true) instead of #WebAppConfiguration.
Fourth, do not extend TestCase: this is JUnit 4, not JUnit 3.
Fifth, do not include the ServletTestExecutionListener: it is only used for out-of-container integration testing, and what you are doing is in-container integration testing (i.e., in an embedded Tomcat Servlet container).
Sixth, if you need the random port number, simply inject it via #Value("${local.server.port}").
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners(
listeners = WithSecurityContextTestExecutionListener.class,
mergeMode = MERGE_WITH_DEFAULTS
)
#SpringApplicationConfiguration({
SecurityWebApplicationInitializerDevelopment.class,
SecurityConfigDevelopment.class,
TomcatEmbededDevelopmentTesting1Profile.class,
Internationalization.class,
MVCConfigDevelopment.class,
PersistenceConfigDevelopment.class,
ServerAuthenticationSuccessHandler.class,
ServerRedirectAuthenticationSuccessHandler.class,
LogicServiceRegistryPostProcessor.class
})
#WebIntegrationTest(randomPort = true)
#ActiveProfiles("Development")
public class QuickTest {
#Value("${local.server.port}")
int port;
// ...
}
So, see if that gets you any further, and read the Spring Boot Reference Manual for further details on Spring Boot's testing support.

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.

Spring Boot: Completely Ignore All Datasources During Unit Tests

I have all kinds of unit tests that mock the repositories they would otherwise need to connect to, yet Spring Boot insists on trying to connect to them.
I've scoured this place, and have seen the suggestions to exclude all the different autoconfiguration classes from each and every one of the test classes I have (or do it globally in /src/test/resources/application.properties) but that doesn't stop Spring Boot from trying to get some kind of data sources going. It seems to just starve it for the stuff it needs to get those datasources going. There doesn't seem to be anything I can do to tell Spring Boot "Do absolutely nothing involving trying to connect to datasources at any time".
I want to be able to do this so I can write and run unit tests on code when I'm not connected to a network. I've considered running H2 and that Flapdoodle whatever, but I've heard that Flapdoodle has problems with later builds of Spring Boot these days, and the H2 module hasn't been updated since 2019.
Can anyone point me in the right direction? Here's a sample test, complete with the exclusions:
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
public class SweepsServiceTest {
#InjectMocks
private SweepsService sweepsService;
#Mock
private PrizeEntryRepository mockPrizeEntryRepository;
#Mock
private PrizeRepositoryPrimary mockPrizeRepositoryPrimary;
#Mock
private PrizeRepositorySecondary mockPrizeRepositorySecondary;
#Mock
private PointService mockPointService;
private final String prizeId = "prize1";
private final String awardId = "award1";
private final int userId = 1234;
private final int costToEnter = 25;
private final int maxEntriesPerUser = 1;
private final int numberToPurchase = 1;
#Test
public void givenPrizeNotFound_whenProcessSweepsEntry_thenThrowException() {
User user = new User();
user.setId(userId);
Mockito.when(mockPrizeRepositoryPrimary.findById(prizeId)).thenReturn(Optional.empty());
assertThatIllegalArgumentException().isThrownBy(
() -> sweepsService.processSweepsEntry(prizeId, "RAFFLE", null, null, user, numberToPurchase)
).withMessage("User " + user.getId() + " tried to enter sweepstakes id " + prizeId + " but that ID was not found.");
}
}
When you don't need to build a spring application context (in case of unit tests) just refrain from using the #SpringBootTest annotation and use JUnit and Mockito exclusively. Otherwise, in case of integration tests, for example, I usually just define which configuration classes I want to include explicitly:
#SpringBootTest(classes = {SomeConfigurationClass.class, AnotherConfigurationClass.class})
class SomeIntegrationTest { ... }
See the JavaDoc for SpringbootTest#classes:
The component classes to use for loading an ApplicationContext. Can also be specified using #ContextConfiguration(classes=...). If no explicit classes are defined the test will look for nested #Configuration classes, before falling back to a #SpringBootConfiguration search.

How to debug, why Spring Boot integration test context is not reused between tests?

I got another tricky question for the crowd. I got two separate test files reusing the same Context class. I would expect them to reuse the same context, alas Spring ist started two times, prolonging the build time. Do you have any ideas how to figure out / debug, what triggers the context reload?
The test classes look like:
#SpringBootTest(
classes = [HttpProxyTestContext::class]
)
#AutoConfigureWireMock(port = 8082)
internal class AuthOpenidConnectSpringIT {
...
}
and
#SpringBootTest(
classes = [HttpProxyTestContext::class]
)
#AutoConfigureWireMock(port = 8082)
internal class AuthOidcWebClientIT {
...
}
The Context class is
#JooqTest
#ComponentScan(basePackages = ["de.denktmit.someproject.springconfig"])
class HttpProxyTestContext {}
Best regards, stay healthy,
Marius Schmmidt
I was finally able to figure out, that the problem somehow arose from using #AutoConfigureWireMock(port = 8082). I found it out by experimenting with the annotations used. Finally I slightly adjusted my test setup and finally my context is reused. Here's how I did it, thats my test only config:
#SpringBootConfiguration
#ComponentScan(basePackages = ["de.denktmit.someproject.springconfig"])
#AutoConfigureJooq
class HttpProxyTestContext {
#Bean(destroyMethod = "stop")
fun wiremock(): WireMockServer {
val wireMockServer = WireMockServer(wireMockConfig().port(8082))
wireMockServer.start()
WireMock.configureFor("localhost", 8082);
return wireMockServer
}
}
And I just gets picked up as easy as
#SpringBootTest
internal class AuthOpenidConnectSpringIT {
...
}
#SpringBootTest
internal class AuthOidcWebClientIT {
...
}
Wiremock #BeforeEach setup left untouched and is the same as before. May it be helpful.
Good night and best regards,
Marius Schmidt

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.

Spring profiles on integration tests class

we have selenium tests which are ran by java test class.
On local environment everything is ok, but I want to switch off those tests when run on jenkins.
So I use:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebIntegrationTest("server.port=1234")
#Profile("!jenkins")
#ActiveProfiles("integrationtests")
public class LoginAndEditProfileSeleniumTest {
...
What works:
running mvn clean test run all tests locally, with integrationtests profile active. I dont want to pass any additional parameter.
What I want to achieve:
running mvn clean test -Dspring.profiles.active=jenkins switch off this test.
Can I merge somehow profile passed by parameter, ActiveProfile annotation and take Profile annotation into consideration? :)
//update:
Its possible to use class extending ActiveProfilesResolver:
public class ActiveProfileResolver implements ActiveProfilesResolver {
#Override
public String[] resolve(Class<?> testClass) {
final String profileFromConsole = System.getProperty("spring.profiles.active");
List<String> activeProfiles = new ArrayList<>();
activeProfiles.add("integrationtests");
if("jenkins".contains(profileFromConsole)){
activeProfiles.add("jenkins");
}
return activeProfiles.toArray(new String[activeProfiles.size()]);
}
}
but it seems to not to cooperate with #Profile anyway ( jenkins profile is active but test is still running ) .
#Profile has zero affect on test classes. Thus, you should simply remove that annotation.
If you want to enable a test class only if a given system property is present with a specific value, you could use #IfProfileValue.
However, in your scenario, you want to disable a test class if a given system property is present with a specific value (i.e., if spring.profiles.active contains jenkins).
Instead of implementing a custom ActiveProfileResolver, a more elegant solution would be to use a JUnit assumption to cause the entire test class to be ignored if the assumption fails.
This should work nicely for you:
import static org.junit.Assume.*;
// ...
#BeforeClass
public static void disableTestsOnCiServer() {
String profilesFromConsole = System.getProperty("spring.profiles.active", "");
assumeFalse(profilesFromConsole.contains("jenkins"));
}
Regards,
Sam (author of the Spring TestContext Framework)

Resources