I developed a system whose infrastructure layer was developed on Spring boot (Security, Data JPA, MVC ...). At runtime the system connects to MySQL whose settings are in src/main/resources/application.properties
along with .sql that contains some user inserts and roles for authentication.
For the integration tests, I decided to use HSQLDB to isolate the data and perform 'safe' testing. For this I created the class AbstractIntegrationTest
that contains methods to create and clean tables and methods to test from the controller. All test classes extend it: (I've hidden methods that do not involve database)
@WebAppConfiguration
@ContextConfiguration(classes={LibraryManagerContextConfiguration.class, WebSecurityConfig.class})
public class AbstractIntegrationTest {
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
private LoginFilter loginFilter;
private MockMvc mockMvc;
private static IDatabaseConnection databaseConnection;
private static Connection connection;
private static boolean isAfterFirstRun;
private static Logger logger = LogManager.getLogger(AbstractIntegrationTest.class);
@BeforeClass
public static void createDatabase() throws Exception {
try {
final Properties properties = loadProperties();
final String driver = properties.getProperty("datasource.driver");
final String url = properties.getProperty("datasource.url");
final String userName = properties.getProperty("datasource.username");
final String password = properties.getProperty("datasource.password");
final String schema = properties.getProperty("datasource.schema");
Class.forName(driver);
connection = DriverManager.getConnection(url, userName, password);
databaseConnection = new HsqldbConnection(connection, schema);
} catch (final SQLException exception) {
throw new RuntimeException(exception.getMessage(), exception);
} catch (final ClassNotFoundException exception) {
throw new RuntimeException(exception.getMessage(), exception);
}
}
@Before
public void setDatabaseUp() throws Exception {
if (!isAfterFirstRun) {
runSQLCommands(getDataSetupFile());
}
runSQLCommands(getClearDatabaseFile());
runSQLCommands(getResetSequencesFile());
runSQLCommands(getDataFile());
isAfterFirstRun = true;
}
@AfterClass
public static void closeConnection() throws Exception {
connection.close();
databaseConnection.close();
}
protected void runSQLCommands(final String file) throws Exception {
if (file != null) {
final InputStream stream = getSQLInputStream(file);
final BufferedReader databaseReader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
int i = 1;
String sqlStatement = null;
while ((sqlStatement = databaseReader.readLine()) != null) {
if (sqlStatement.startsWith("--")) {
i++;
continue;
}
final int index = sqlStatement.lastIndexOf(";");
if (index > -1) {
sqlStatement = sqlStatement.substring(0, index + 1);
}
if (sqlStatement.trim().length() != 0) {
try {
connection.createStatement().execute(sqlStatement);
logger.info(sqlStatement);
} catch (final Exception exception) {
logger.error("Error running command on line " + i + " of file " + file + ": " + exception.getMessage());
throw new RuntimeException(exception);
}
}
i++;
}
}
}
protected IDatabaseConnection getConnection() {
return databaseConnection;
}
protected static IDataSet getDataSet(final String dataset) {
try {
final InputSource source = new InputSource(AbstractIntegrationTest.class.getResourceAsStream(dataset));
return new FlatXmlDataSetBuilder().build(source);
} catch (final Exception exception) {
throw new RuntimeException("Cannot read the dataset file " + dataset + "!", exception);
}
}
private static Properties loadProperties() throws Exception {
final InputStream stream = ClassLoader.getSystemResourceAsStream("datasource.properties");
if (stream == null) {
throw new FileNotFoundException("File erm.properties not found. A file named erm.properties must be present "
+ "in the src/test/resources folder of the project whose class is being tested.");
}
final Properties properties = new Properties();
properties.load(stream);
return properties;
}
private static InputStream getSQLInputStream(final String fileName) {
return AbstractIntegrationTest.class.getResourceAsStream(fileName);
}
protected String getClearDatabaseFile() {
return "/database/clear-database.sql";
}
protected String getDataSetupFile() {
return "/database/database-setup.sql";
}
protected String getDataFile() {
return "/database/data.sql";
}
protected String getResetSequencesFile() {
return "/database/reset-sequences.sql";
}
}
The LibraryManagerContextConfiguration
and WebSecurityConfig
classes contain the domain and infrastructure beans declarations so they make up the spring context.
This class is in src/test/java
and the datasource.properties
file together with the test .sql are in src/test/resources
. The test classes run perfectly, the test scripts run, the tables are created, but when a Repository looks for some data during the test it searches MySQL instead of HSQLDB. Here's a test class:
@RunWith(SpringJUnit4ClassRunner.class)
public class AuthenticationIntegrationTest extends AbstractIntegrationTest {
@Test
public void shouldGetAuthorizationJwt() throws Exception {
final String jsonCredentials = "{"
+ "\"username\" : \"augusto\","
+ "\"password\" : \"spring\""
+ "}";
final MvcResult result = performRESTLogin(jsonCredentials);
final MockHttpServletResponse response = result.getResponse();
final int status = response.getStatus();
final String jwt = response.getHeader("Authorization");
assertThat(status, is(200));
assertThat(jwt, notNullValue());
}
}
I verified this when I used username and password that existed only on the test database and got 403
status whereas with MySQL values got 200
status.
It seems that after preparation of HSQLDB the .properties and .sql of main are read and override the settings of the database being used.
application.properties:
server.contextPath=/librarymanager
server.port: 8081
spring.datasource.url = jdbc:mysql://localhost:3306/librarymanager
spring.datasource.username = root
spring.datasource.password = root
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = create-drop
spring.jpa.hibernate.naming.strategy = org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
datasource.properties:
datasource.class=org.hsqldb.jdbc.JDBCDataSource
datasource.driver=org.hsqldb.jdbc.JDBCDriver
datasource.url=jdbc:hsqldb:mem:librarymanager;sql.syntax_ora=true
datasource.schema=sa
datasource.username=sa
datasource.password=
DB Dependencies in pom.xml:
<!-- Banco de dados -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>test</scope>
</dependency>
What is missing for the execution to be successful? The use of an annotation? Create .properties for dev and tests separate with the BDs conf and a main .properties with spring.profiles.active = dev/test
to switch between profiles? I would like some suggestions.
Thanks.
Project link in github: https://github.com/augustodossantosti/librarymanager-jwtauth
Thanks for the suggestions. Actually working with the framework is the best option.