I seem to be running into a tricky situation with ordering in one of my integration tests. I have a REST server based on Spring Boot/Spring MVC and a Cassandra DB. I'm using the spring-data-cassandra jar file to enable POJOs to be inserted into the DB through the CassandraTemplate via a CrudRepository implementation. The application works fine, I can make REST calls and the framework(s) correctly convert my form data to POJOs and inserts the data in the DB. So far, so good.
What I'm trying to do is write an integration test that executes the entire stack. Obviously I don't want to depend on an external DB being available, so I'm using the EmbeddedCassandra stuff from cassandra-unit-spring and drive it through a Spock test. The problem I run into seems to have to do with some sort of ordering conflict between the SpringApplicationContextLoader defined in the @ContextConfiguration annotation, and the CassandraUnitTestExecutionListener class defined in the @TestExecutionListener annotation. Since this is a Spock test, it needs the SpringApplicationContextLoader class to automatically start the Spring Boot server. However, it does so before the Cassandra server has started up, which causes the Cluster/Session beans to fail to load since they apparently attempt to contact the server immediately upon creation. I have tried so many combinations of things that my head is spinning. I had to resort to what I consider a pretty ugly solution. I have a setup() method that uses a boolean to make sure the context and Spring Boot app is only run once. I tried putting this in a setupSpec() method that is only run once per test class, but that didn't work (some catch 22 problem). If I try to @Autowire the context, then it gets injected before the CassandraServer has started. As I mentioned, I've tried a gazillion different things.
This solution works, but it bugs me that I couldn't get it to work in a more elegant fashion with just annotations and DI. Also, the @Value annotated fields don't get initialized, which is why I had to resort to the kludge of obtaining the server port through the environment after starting Tomcat. Any suggestions on how to do this "the right way" are very welcome. Thanks.
@ActiveProfiles(profiles = ["test"])
@ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = Application.class)
// @WebIntegrationTest("server.port:0") // Pick a random port for Tomcat
@EnableConfigurationProperties
@EmbeddedCassandra
@CassandraDataSet(value = ["cql/createUserMgmtKeySpace.cql", "cql/createUserMgmtTables.cql"], keyspace = "user_mgmt")
@TestExecutionListeners(listeners = [ CassandraUnitTestExecutionListener.class ]) //, DependencyInjectionTestExecutionListener.class ]) //, mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
@Title("Integration test for the lineup ingestion REST server and the Cassandra DAOs.")
public class CassandraSpec extends Specification {
@Shared
ApplicationContext context
@Shared
CQLDataLoader cql
@Shared boolean initialized = false
// This is the server port that Spring picked. We use it in the REST URLs
// @Value('${local.server.port}')
@Shared
int serverPort
def setup() {
if (initialized) {
return
}
System.setProperty("spring.profiles.active", "test")
SpringApplication app = new SpringApplication(CassandraConfig.class)
app.headless = true
context = app.run(Application.class)
assert context
cql = new CQLDataLoader(context.getBean("session"))
serverPort = Integer.parseInt(context.environment.getProperty('server.port'))
println("Tomcat port: ${serverPort}")
initialized = true
}
... test methods here ...
}