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 ...
}
Face palm!!! After posting this, I found out that I needed to add CassandraUnitDependencyInjectionTestExecutionListener. The magic line is:
@TestExecutionListeners(listeners = [ CassandraUnitDependencyInjectionTestExecutionListener, CassandraUnitTestExecutionListener, DependencyInjectionTestExecutionListener.class ])
After that I was able to run the test without any of the other kludges, and I can inject an ephemeral port and use it in my RestTemplate URLs. Problem solved :-)
The answer above provided by @user2337270 is the correct one. Though I am not using the Spock testing framework, I spent a fair bit of time trying to get cassandra unit and spring to work. Adding CassandraUnitDependencyInjectionTestExecutionListener
did the trick. I thought it may be helpful to other users to provide a sample of a full test class that works. Hope it helps.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { CassandraConfig.class, InboundDocumentCassandraDaoTest.CassandraPropertiesTest.class })
@EmbeddedCassandra
@CassandraDataSet(value = {"idoc.cql"}, keyspace = "idoc")
@TestExecutionListeners(listeners = { CassandraUnitDependencyInjectionTestExecutionListener.class, DependencyInjectionTestExecutionListener.class })
public class InboundDocumentCassandraDaoTest {
@Autowired
CassandraOperations cassOp;
@Autowired
InboundDocumentDao dao;
@Test
public void insert_into_idocs() throws Exception {
UUID id = UUIDs.timeBased();
long addedDate = System.currentTimeMillis();
Insert insert = QueryBuilder.insertInto("idocs");
insert.setConsistencyLevel(ConsistencyLevel.ONE);
insert.value("idoc_id", id);
insert.value("idoc_added_date", addedDate);
insert.value("idoc_type", "invoice");
insert.value("json_doc", "{ \"foo\":\"bar\" }");
cassOp.execute(insert);
String jsonObj = dao.getInboundDocumentById(id.toString());
assertNotNull(jsonObj);
assertEquals("{ \"foo\":\"bar\" }", jsonObj);
}
@Configuration
public static class CassandraPropertiesTest {
@Bean
public CassandraProperties cassadraProps() {
CassandraProperties props = new CassandraProperties();
props.setCassandraContactpoints("127.0.0.1");
props.setCassandraPort("9142");
props.setCassandraKeyspace("idoc");
props.setReadConsistencyLevel("LOCAL_ONE");
return props;
}
}
}
As an FYI, the idoc.cql file was placed in the src/main/resources
folder.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With