Some tests fails with gradle test command (Spring Boot 3) - spring-boot

My environment is Spring Boot 3.0.2, Gradle and Java 17. I implemented an application with more than 300 unit and integration tests.
After 200 tests my suite became unstable. If I launch the tests from intellij they all work correctly. If I run the tests one by one from the gradle command they also work fine. But if I run all the tests together, some randomly fail.
The error is that it fails to load certain mockbeans.
If I put this configuration in the build.gradle then all the tests go well, but it takes a long time.
tasks.named('test') {
forkEvery = 1
useJUnitPlatform()
}
If I put this other configuration, then other tests fail
tasks.named('test') {
maxParallelForks = Runtime.runtime.availableProcessors()
useJUnitPlatform()
}
Could it be a memory problem? parallel processes or something similar? It is very strange, the truth that does not give me any confidence.
For example, a test class.
package ...
import ...
#SpringBootTest
#ActiveProfiles("test")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class OfferCreateTest {
#Autowired
PropertyCreate propertyCreate;
#Autowired
OfferCreate offerCreate;
#MockBean
ObHttpClient obHttpClient;
#MockBean
RoomtypeFind roomtypeFind;
#MockBean
RateplanFind rateplanFind;
#MockBean
PropertyChannelFind propertyChannelFind;
private Long propertyId;
#BeforeAll
void setup(){
propertyId = createProperty();
}
private Long createProperty(){
PropertyCreateDto propertyCreateDto = PropertyCreateDtoMother.randomWithFullData();
return propertyCreate.create(propertyCreateDto).getResult().get("id");
}
#Test
void create_offer(){
OfferCreateDto offerCreateDto = OfferCreateDtoMother.random();
Long offerId = offerCreate.create(propertyId, offerCreateDto).getResult().get("id");
assertTrue(offerId >= 100000L);
}
#Test
void create_offer_on_non_existing_property(){
OfferCreateDto offerCreateDto = OfferCreateDtoMother.random();
assertThrows(Exception.class, () -> offerCreate.create(ContentGenerator.customIdLower(), offerCreateDto));
}
}
Very thanks,

Related

How to stop #CucumberContextConfiguration with #SpringBootTest from reloading application context between every test?

I've got this problem where my application context is reloaded between every test. I'm wiring in my actual application with functional test properties, wiremock etc. to create a functional test environment. Tests have always run fine but now we've added several it's become painfully slow due to the spring application being re-run everytime. The io.cucumber versions I'm using in my pom for cucumber-spring, cucumber-java, cucumber-junit is 7.11.1.
My Functional Test runner is annotated like this:
#RunWith(Cucumber.class)
#CucumberOptions(
features = "classpath:functional/features",
glue = {"com.iggroup.ds.functional.stepdefinitions"},
monochrome = true,
tags = "#FunctionalTest",
plugin = {"pretty", "html:target/cucumber-html-report", "junit:target/cucumber-xml-report.xml"}
)
public class FunctionalTestRunner {
#BeforeClass
public static void beforeClass() {
prepareEnvironment();
}
private static void prepareEnvironment() {
int applicationPort = SocketUtils.findAvailableTcpPort();
System.setProperty("server.port", String.valueOf(applicationPort));
System.setProperty("spring.active.profiles", "FUNCTIONAL_TEST");
System.setProperty("spring.cloud.config.enabled", "false");
System.setProperty("spring.cloud.config.server.bootstrap", "false");
}
}
Inside my glue package the Cucumber Configuration looks like this:
#AutoConfigureWireMock(port = 8089)
#CucumberContextConfiguration
#SpringBootTest(
classes = {
ServiceApplication.class,
RestClients.class
},
webEnvironment = DEFINED_PORT,
properties = {
"spring.profiles.active=FUNCTIONAL_TEST",
"spring.cloud.config.enabled = false"
}
)
public class FunctionalTestSpringCucumberConfiguration {
}
And lastly the application itself looks like this:
#EnableAsync
#EnableCaching
#EnableConfigServer
#SpringBootApplication
#EnableConfigurationProperties
public class ServiceApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
I had read somewhere before that the presence of #MockBean was causing unexpected refreshes between context although I never found out as to why - but I have none defined. As far as I can tell across the articles I've been reading, this shouldn't refresh my context every time so wondering if there's any way I can force it not to rewire the ServiceApplication.class in between every scenario?
#AutoConfigureWireMock(port = 8089)
By using Wiremock on fixed port you are dirtying the application context. This means a new application context will be created for each test. The code responsible for this prints a warning that you can see in your logs.
if (portIsFixed(testContext)) {
if (log.isWarnEnabled()) {
log.warn("You've used fixed ports for WireMock setup - "
+ "will mark context as dirty. Please use random ports, as much "
+ "as possible. Your tests will be faster and more reliable and this "
+ "warning will go away");
}
testContext.markApplicationContextDirty(DirtiesContext.HierarchyMode.EXHAUSTIVE);
}

Spring MockMvc second test fails when using StreamingResponseBody

I have a simple Spring Boot service that uses StreamingResponseBody to write the response.
When testing this with MockMvc, the first test gives me the correct response, but the second one has an empty response body.
However, when I add a little sleep time to the test, they both work.
Here is my controller (Kotlin)
#RestController
class DummyController() {
#GetMapping()
fun streamIndex() =
ResponseEntity.ok()
.contentType(MediaType.TEXT_PLAIN)
.body(StreamingResponseBody { it.write("Hello world".toByteArray()) })
}
And my tests:
#AutoConfigureMockMvc(print = MockMvcPrint.NONE)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DummyControllerTest {
private val controller = DummyController()
private val mvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(GlobalExceptionHandler()).build()
#Test
fun test1() {
mvc.perform(get("/")).andExpect(status().isOk).andExpect(content().string("Hello world"))
}
#Test
fun test2() {
mvc.perform(get("/")).andExpect(status().isOk).andExpect(content().string("Hello world"))
}
}
Thee first test succeeds, the second one fails with:
java.lang.AssertionError: Response content expected:<Hello world> but was:<>
Then when I add a little delay in the test it works:
#Test
fun test2() {
mvc.perform(get("/")).andExpect(status().isOk)
.andDo { Thread.sleep(5) }
.andExpect(content().string("Hello world"))
}
Is this a bug in MockMvc or something missing in my test?
Thanks in advance,
Rob
Instead of adding a delay, you will need to fetch the async result first by doing the following:
#Test
fun test2() {
mvc.perform(get("/"))
.andExpect(request().asyncStarted())
.andDo(MvcResult::getAsyncResult)
.andExpect(status().isOk)
.andExpect(content().string("Hello world"))
}
Make sure you add this to both tests.
Source: https://github.com/spring-projects/spring-framework/blob/main/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java #line 81
Your test is wrong/weird. You have #SpringBootTest which starts an app on the port, have configured #AutoConfigureMockMvc and totally ignore all of that.
The combination in your #SpringBootTest wouldn't even work as you cannot use RANDOM_PORT with MockMvc (that only works with a MOCK environment). You either use the MOCK environment or switch to TestRestTemplate/TestWebClient for testing.
You probably worked around the issues you had with manually constructing a controller and manually registering MockMvc with that controller.
Finally you are using an async request which you would need to consume properly as well. See async testing in the Spring Reference Guide. It also shows another way of testing using the WebTestClient which is preferred for streaming responses.
In short your test is a bit af a frankenstein test.
I would strongly suggest to use the technologies in a proper way and rewrite your test as follows.
#WebMvcTest(DummyController.class)
class DummyControllerTest {
#Autowired
private val mvc;
#Test
fun test1() {
var mvcResult = mvc.perform(get("/"))
.andExpect(request().asyncStarted())
.andExpect(status().isOk)
.andExpect(request().asyncResult("Hello world"))
}
#Test
fun test2() {
mvc.perform(get("/"))
.andExpect(request().asyncStarted())
.andExpect(status().isOk)
.andExpect(request().asyncResult("Hello world"))
}
}
This test won't start a full blown container but only the Web MVC stuff and the stuff needed for your controller. In the tests the async result is now also properly parsed and asserted. All in all it should now also run a lot faster due to the reduced things needed to start (unless there wasn't much to start with).

How to write script in gradle that execute particular methods?

I am writing a gradle script that runs all tests before making a build.
test {
filter {
includeTestsMatching "*TestAll*"
includeTestsMatching "*ExtensionValidatorTest*"
........
}
}
I have three tests of different versions(v1,v2,v3).
TestAll.java
package .....v1;//v2 for version 2 and v3 for version 3
#RunWith(Suite.class)
#Suite.SuiteClasses({
A.class,
B.class,
......
})
public class TestAll {
#BeforeClass
public static void setUp() {//connection to database
........
}
#AfterClass
public static void tearDown() {//close database connection
........
}
}
When I run gradle test connection to database is broken after execution of a particular TestAll. I do not want to change the TestAll files of any version as they can be run and tested independently. How can I make gradle run only setUp once(of any version)which establishes connection, then run all the TestAll method in v1,v2 and v3 and finally teardown(of any version) which terminates database connection.
Gradle won't help you with this. There are following methods in Gradle DSL:
test {
beforeSuite{...}
afterSuite{...}
}
However, they execute outside of the test runtime scope and intended for logging. You only can achieve this using a testing framework.
TestNG provides a simple solution - #BeforeSuite and #AfterSuite annotations, that are actually run once before and after the entire suite.
Unfortunately, JUnit doesn't have a built-in solution for that, since test isolation is its core concept. Nevertheless, you still can make your own. You need to encapsulate database-related API into a singleton class:
public class DbContainer() {
private static DbContainer container;
private DbContaner() {}
public DbContainer getInstance() {
if (container == null) {
container = new DbContainer()
}
return container;
}
public void openConnection() {
// ...
}
public void closeConnection() {
// ...
}
// here is your database API methods
}
Then you can share this instance between test methods and classes using #ClassRule annotation:
#ClassRule
public static DbContainer db = DbContainer.getInstance();
#Test
public void someTest() {
db.query(...)
}
Note: provided solution is not thread-safe and doesn't suit the parallel execution. Some additional effort is required to achieve it.

Roboelectric with Room Database for android

How to do unit testing of Room Database with the help of Roboeletric?
I don't want to do instrumentation testing.
From what I can tell it can be done like this
//#RunWith(AndroidJUnit4::class)
#RunWith(RobolectricTestRunner::class)
class WordDaoTest {
private lateinit var wordRoomDatabase: WordRoomDatabase
private lateinit var wordDao: WordDao
#get:Rule
var instantTaskExecutor = InstantTaskExecutorRule()
#Before
fun createDb() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
wordRoomDatabase = Room.inMemoryDatabaseBuilder(context, WordRoomDatabase::class.java).allowMainThreadQueries().build()
wordDao = wordRoomDatabase.wordDao()
wordRoomDatabase.wordDao().insertAll(listOf<Word(Word("one"),Word("two"),Word("three"))
}
#After
fun closeDb() {
wordRoomDatabase.close()
}
#Test
fun testGetName() {
Assert.assertThat(getValue(wordDao.getAllLiveWords()).size, equalTo(3))
}
}
It seems though you need allowMainThreadQueries() in the build of the DB.
I am not sure as to why everyone tests the Dao in the instrumentation test when it can be done in the Unit Test and then be added to code coverage (maybe someone else has some insight)
This code is in Kotlin but I am sure it would translate to java just the same.
This was provided to me however as to why it's not considered to be best practice
https://developer.android.com/training/data-storage/room/testing-db
Note: Even though this setup allows your tests to run very quickly, it isn't recommended because the version of SQLite running on your device—and your users' devices—might not match the version on your host machine.
Robolectric can support such JVM unit tests with Room.
To get the required context, please add below dependency in your build.gradle:
testImplementation 'androidx.test:core:1.2.0'
Let's assume that we have a repository class which wraps the Room Dao. And here's a quick example:
#RunWith(RobolectricTestRunner::class)
#Config(sdk = [28]) // This config setting is the key to make things work
class FooRepositoryTest {
#get:Rule
var instantTask = InstantTaskExecutorRule()
private lateinit var database: MyDatabase
private lateinit var sut: FooRepository
#Before
fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>()
database = Room.inMemoryDatabaseBuilder(context, MyDatabase::class.java)
.allowMainThreadQueries()
.build()
sut = FooRepository(database.fooDao())
}
#After
fun tearDown() {
database.close()
}
}

How to run test Containers with spring boot and spock

I want to use test container with spock on my spring boot application.
these are my dependencies :
dependencies {
compile('org.springframework.boot:spring-boot-starter-data-redis')
compile('org.springframework.boot:spring-boot-starter-web')
compile 'org.testcontainers:spock:1.8.3'
runtime('org.springframework.boot:spring-boot-devtools')
compile 'org.codehaus.groovy:groovy-all:2.4.15'
compileOnly('org.projectlombok:lombok')
compile 'org.testcontainers:testcontainers:1.8.3'
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile "org.spockframework:spock-core:1.1-groovy-2.4-rc-4"
testCompile "org.spockframework:spock-spring:1.1-groovy-2.4-rc-4"
testCompile 'com.github.testcontainers:testcontainers-spock:-SNAPSHOT'
}
I have initialized my test like below :
#SpringBootTest
#Testcontainers
class ProductRedisRepositoryTest extends Specification {
#Autowired
ProductRedisRepository productRedisRepository
#Autowired
TestComponent testComponent
static Consumer<CreateContainerCmd> cmd = { -> e.withPortBindings(new PortBinding(Ports.Binding.bindPort(6379), new ExposedPort(6379)))}
#Shared
public static GenericContainer redis =
new GenericContainer("redis:3.0.2")
//.withExposedPorts(6379)
.withCreateContainerCmdModifier(cmd)
def "check redis repository save and get"(){
given:
Product product = Product.builder()
.brand("brand")
.id("id")
.model("model")
.name( "name")
.build()
when:
productRedisRepository.save(product)
Product persistProduct = productRedisRepository.find("id")
then:
persistProduct.getName() == product.getName()
}
}
But it doesn't initiate redis container when I run test.
What is my mistake. How can I do that.
My springBootVersion = '2.0.4.RELEASE' and I am using Intelij.
This is the log output : LOG
Please remove the static keyword at your #Shared GenericContainer field.
Static fields won't be annotated with #FieldMetadata by the Spock Framework, hence they won't be considered as part of the spec's fields.
Testcontainers-Spock relies on those fields to recognise GenericContainers.
If you need the static modifier though, you can work around the issue like this:
...
public static GenericContainer staticRedis =
new GenericContainer("redis:3.0.2")
//.withExposedPorts(6379)
.withCreateContainerCmdModifier(cmd)
#Shared
public GenericContainer redis = staticRedis
...

Resources