My Spring Boot application also starts a gRPC service along with its REST (HTTP) service. I've written specific tests for gRPC and REST. When I run a gradle test these tests are run sequentially, however; there is no reason they can't be run in parallel.
What I'm shooting for here is a single instance of my Spring Boot application running while the tests are executed in parallel.
I've tried setting the test section in my gradle file so it has 'forkCount', I also tried setting options such that parallel="classes", but this produces an error about the 'parallel' being an unknown property (maybe a junit 5 thing?)
test {
options {
parallel = "classes"
// forkCount = 2
}
}
The forkCount option is not what I'm looking for since it will start multiple instances of the spring application.
I've also tried removing the #RunWith from the test classes and making a separate test class (which has the #RuWith annotation) that has the following method in it
#Test
void testRunner() {
JUnitCore.runClasses(ParallelComputer.classes(), {GrpcTests.class, RestTests.class});
}
But the tests still appear to run sequentially.
I've tried several other things as well, sorry I don't have all of them handy.
Goal
Ideally what I'm hoping for is a single instance of my Spring Boot app running while the test classes run in parallel (bonus kudos if I can get the methods to run in parallel too)
Java Version: "1.8.0_171"
Spring Boot Version: 2.0.4.RELEASE
Per the recommendation I tried adding the
#Test
public void contextLoads() throws Exception {
}
And adding the 'maxParallelForks' entry in the gradle file, I had already been using the #SpringBootTest annotation but this behaved the same as when I used 'forkCount` in that at least 2 instances where started as can be seen by the test shutdown log
2019-04-25 10:24:17.245 LogLevel=INFO 53838 --- shutting down gRPC server since JVM is shutting down
...
2019-04-25 10:24:30.125 LogLevel=INFO 53839 --- shutting down gRPC server since JVM is shutting down
You can see I get two shutdown messages and the PIDs are shown (53838 & 53839).
You need to combine #SpringBootTest with maxParallelForks.
Annotate your unit tests with #SpringBootTest. #SpringBootTest will boot up a Spring Boot context that will be cached across all your tests.
"A nice feature of the Spring Test support is that the application context is cached in between tests, so if you have multiple methods in a test case, or multiple test cases with the same configuration, they only incur the cost of starting the application once"
See:
https://spring.io/guides/gs/testing-web/
Add the following to your build.gradle. To run multiple test at the same time.
tasks.withType(Test) {
maxParallelForks = 4 //your choice here
}
See https://guides.gradle.org/performance/#parallel_test_execution
Related
I am using Testcontainers to execute integration tests in a Spring project. I am using JUnit 4.x and Cucumber 7.4.1.
I want to start a docker-compose.yml-based set of containers before each test, as that makes it easy to start from scratch. Hence, I am using #DirtiesContext.
The challenge here is that certain application properties, such as spring.rabbitmq.host, are needed before the actual application context can start. So I need to inject them beforehand. There is #DynamicPropertySource for that. But then I also need to get access to my context-scoped docker containers. The best I came up with so far is the following:
#CucumberContextConfiguration
#SpringBootTest(classes = TestConfig.class)
#DirtiesContext
public class CucumberITConfig {
#DynamicPropertySource
private static void properties(DynamicPropertyRegistry registry) {
DockerComposeContainer container = new DockerComposeContainer(new File("docker-compose.yml"))
.withExposedService("rabbitmq", 5672);
container.start();
registry.add("spring.rabbitmq.host", () -> container.getServiceHost("rabbitmq", 5672));
}
}
This constructs new docker containers locally and waits for the host to be passed to the registry. While this seem to work, this looks more like a hackish approach to me. Also, a problem here is that the containers stack up after each test. That is, in the 7th test, for example, the containers from all previous 6 cycles are still running.
Are there better approaches to start Testcontainers-based docker containers before each application context, while also being able to destruct them afterwards?
If you are using #DirtiesContext after all, you can set these values as System properties and omit #DynamicPropertySource altogether. However, as others have pointed out, solving test pollution by re-creating the Spring context and all dependent services for every test class will be very slow and is generally considered an anti-pattern.
I am setting up integration tests for my spring-boot application using cassandra-unit-spring maven dependency. I am able to run my tests which invoke the spring-boot application which in turn accesses an in-memory embedded Cassandra database.
Below is the code for my test class
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners({CassandraUnitDependencyInjectionTestExecutionListener.class, DependencyInjectionTestExecutionListener.class})
#CassandraDataSet(value = "cassandra/dbcreate.cql", keyspace = "test")
#EmbeddedCassandra
#SpringBootTest({"spring.data.cassandra.port=9142"})
public class IntegrationTest {
#Autowired
private TestRepository testRepository;
#Test
public void testFindById() {
Token token = generateRandomToken();
testRepository.insert(token);
Optional<Token> tokenStored = testRepository.findById(token.getKey());
compareReplayToken(token, tokenStored.get()); //This method does the assertions
}
}
This single test invokes the embedded Cassandra and creates the keyspace and tables from the commands in the cassandra/dbcreate.cql file. After the test runs, the keyspace and tables are dropped.
Till now, it is fine. But, if I try to add multiple tests in this class, this approach creates the keyspace and tables at the beginning of each test and then drops them once the test runs.
And the dbcreate.cql file has a lot of commands to create multiple tables and when these commands run for each test, this makes my tests really slow.
Also, this problem multiplies when I try to have multiple such test classes.
Possible solution that I could think of is:
Have a separate cql file for each test class that has limited cql commands concerned with that class only - Again, this doesn't solve the problem of the database reset for each test in a single class
I want to run all my integration tests for a single launch of this embedded Cassandra and the tables and keyspace should be created and dropped only once for a fast execution
What should be the ideal solution for such a problem?
Any help is much appreciated.
Thanks!
I intend to write integration tests to check whether my listener/handlers work properly.
I am using Spring Boot 2.1.9.RELEASE with the according amqp dependency.
I have written a custom sender (publisher) and listener (receiver). When conducting the publishing test I am able to debug into my publisher but I am not able to debug into my receiver which I tried to annotate with #RabbitListener(queues = "myQueue") on method level and with #RabbitListener(queues = "myQueue") on class level in conjunction with #RabbitHandler on method level.
What do I have to prepare to run such integration tests successfully?
I have already googled a lot finding some suggestions using RabbitListenerTestHarness and so on but this never worked. For RabbitListenerTestHarness I always get the error that no such bean could be found.
I guess am trying to get a corner case to work here. In my current project there are about 20 integration tests. One new integration test requires #EnableAsync to make the test work:
#RunWith(SpringRunner.class)
#EnableAsync
#SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class MyITest {
:
}
When run alone, this test works fine.
Considering Maven and Eclipse' execution of tests in one project and knowing that the environment is only created once and reused (or soft-reset) for all integration tests, it's somewhat a requirement that this integration test runs first. However, that's (nearly?) never the case.
Therefore, this integration test (nearly?) always fails. One obvious solution is to add #EnableAsync to all integration tests. However, that's a bad dependency which I bet is broken once somebody adds another integration test and forgets this requirement.
I'm looking for a way to force the SpringRunner to completely reset the context and really start it from scratch also looking at #EnableAsync. Ideally that way includes to flag that SpringRunner has to reset the context (i.e., remove the #EnableAsync) after the test, too. That way any order of execution would ensure that only that very one test has the #EnableAsync.
Is there a way to do this? Or can I manually turn on/off the async-stuff by code in a #Before/#After method?
take a look at DirtiesContext
Not sure if this is what you're looking for.
Possible duplicate of: How do you reset Spring JUnit application context after a test class dirties it?
Whow, I think I just found out by accident... What I have now:
#RunWith(SpringRunner.class)
#EnableAsync
#SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = {
ClassWithAnAutowiredAsyncDependency.class // <=== difference!!! ===>
})
public class MyITest {
:
#Autowired
private ClassWithAnAutowiredAsyncDependency mine;
:
}
It seems as if the given classes are reset (specially?) or at least the autowiring happens in there again or something. I can't explain it any different.
I'm sure that this integration test is not the first integration test being run and still the asynchronous bit seems to be in place.
Well, test is green, it works...
Hi I don't understand what advantages give me usage of #ContextHierarchy like below:
#ContextHierarchy({
#ContextConfiguration("/test-db-setup-context.xml"),
#ContextConfiguration("FirstTest-context.xml")
})
#RunWith(SpringJUnit4ClassRunner.class)
public class FirstTest {
...
}
#ContextHierarchy({
#ContextConfiguration("/test-db-setup-context.xml"),
#ContextConfiguration("SecondTest-context.xml")
})
#RunWith(SpringJUnit4ClassRunner.class)
public class SecondTest {
...
}
over usage of single #ContextConfiguration with locations argument, like below:
#ContextConfiguration(locations = {"classpath:test-db-setup-context.xml", "FirstTest-context.xml", "SecondTest-context.xml" })
In each case, application contexts are shared across diffrent junit test classes.
The difference is that beans in each context within the context hierarchy can't see beans in the other the context. So you can isolate different parts of your item under test.
An imortant thing to note here is that In case of #ContextHierarchy we get SEPARATE contexts that have SEPARATE life-cycles (initialization, shutdown). This is important because for example they can fail independently.
A practical example from my yard. We have a Spring application that communicates with some external services. We wanted an E2E test that starts these dependent services and the runs the tests. So we added an initializer to our #ContextConfiguration:
#ContextConfiguration{classes = TheApp.class, initializers = DockerInitializer.class}
public class TheAppE2ETests {
// ...
}
The initializer was preparing the external services (starting Dockers), customizing the properties so that The App can run and attaching to the close context event so that the Dockers could be cleaned up. There was a problem with this approach when The App context was failing to load (e.g. due to a bug):
After a failed initialization the ContextClosedEvent is not fired - the dockers were not stopped and cleaned up.
When the context fails to load the initializer is called over and over for every test which is run (not only for every test class - for every test method!).
So the tests kept on killing our CI environment every time a bug in The App's context caused the initialization to fail. The containers for the dependent services were started for every single test method and then not cleaned up.
We ended up using #ContextConfiguration and having two separate contexts for dockers and The App itself. This way in case of an above-mentioned situation the dockers are started in a separate context and therefore live there own live and can even be shared across multiple Spring tests (due to Spring's context caching mechanism).