Skip to content

Commit 64c55f9

Browse files
committed
Add MySQL container implementation under org.testcontainers.mysql
1 parent 0102bcb commit 64c55f9

File tree

6 files changed

+201
-60
lines changed

6 files changed

+201
-60
lines changed

docs/modules/databases/mysql.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Testcontainers module for [MySQL](https://hub.docker.com/_/mysql)
77
You can start a MySQL container instance from any Java application by using:
88

99
<!--codeinclude-->
10-
[Container definition](../../../modules/mysql/src/test/java/org/testcontainers/junit/mysql/SimpleMySQLTest.java) inside_block:container
10+
[Container definition](../../../modules/mysql/src/test/java/org/testcontainers/mysql/MySQLContainerTest.java) inside_block:container
1111
<!--/codeinclude-->
1212

1313
See [Database containers](./index.md) for documentation and usage that is common to all relational database container types.

modules/mysql/src/main/java/org/testcontainers/containers/MySQLContainer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
* Supported image: {@code mysql}
1313
* <p>
1414
* Exposed ports: 3306
15+
*
16+
* @deprecated use {@link org.testcontainers.mysql.MySQLContainer} instead.
1517
*/
18+
@Deprecated
1619
public class MySQLContainer<SELF extends MySQLContainer<SELF>> extends JdbcDatabaseContainer<SELF> {
1720

1821
public static final String NAME = "mysql";
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package org.testcontainers.mysql;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
import org.testcontainers.containers.ContainerLaunchException;
5+
import org.testcontainers.containers.JdbcDatabaseContainer;
6+
import org.testcontainers.images.builder.Transferable;
7+
import org.testcontainers.utility.DockerImageName;
8+
9+
import java.util.Set;
10+
11+
/**
12+
* Testcontainers implementation for MySQL.
13+
* <p>
14+
* Supported image: {@code mysql}
15+
* <p>
16+
* Exposed ports: 3306
17+
*/
18+
public class MySQLContainer extends JdbcDatabaseContainer<MySQLContainer> {
19+
20+
public static final String NAME = "mysql";
21+
22+
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mysql");
23+
24+
static final String DEFAULT_USER = "test";
25+
26+
static final String DEFAULT_PASSWORD = "test";
27+
28+
private static final String MY_CNF_CONFIG_OVERRIDE_PARAM_NAME = "TC_MY_CNF";
29+
30+
public static final Integer MYSQL_PORT = 3306;
31+
32+
private String databaseName = "test";
33+
34+
private String username = DEFAULT_USER;
35+
36+
private String password = DEFAULT_PASSWORD;
37+
38+
private static final String MYSQL_ROOT_USER = "root";
39+
40+
public MySQLContainer(String dockerImageName) {
41+
this(DockerImageName.parse(dockerImageName));
42+
}
43+
44+
public MySQLContainer(final DockerImageName dockerImageName) {
45+
super(dockerImageName);
46+
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
47+
48+
addExposedPort(MYSQL_PORT);
49+
}
50+
51+
/**
52+
* @return the ports on which to check if the container is ready
53+
* @deprecated use {@link #getLivenessCheckPortNumbers()} instead
54+
*/
55+
@NotNull
56+
@Override
57+
@Deprecated
58+
protected Set<Integer> getLivenessCheckPorts() {
59+
return super.getLivenessCheckPorts();
60+
}
61+
62+
@Override
63+
protected void configure() {
64+
optionallyMapResourceParameterAsVolume(
65+
MY_CNF_CONFIG_OVERRIDE_PARAM_NAME,
66+
"/etc/mysql/conf.d",
67+
"mysql-default-conf",
68+
Transferable.DEFAULT_DIR_MODE
69+
);
70+
71+
addEnv("MYSQL_DATABASE", databaseName);
72+
if (!MYSQL_ROOT_USER.equalsIgnoreCase(username)) {
73+
addEnv("MYSQL_USER", username);
74+
}
75+
if (password != null && !password.isEmpty()) {
76+
addEnv("MYSQL_PASSWORD", password);
77+
addEnv("MYSQL_ROOT_PASSWORD", password);
78+
} else if (MYSQL_ROOT_USER.equalsIgnoreCase(username)) {
79+
addEnv("MYSQL_ALLOW_EMPTY_PASSWORD", "yes");
80+
} else {
81+
throw new ContainerLaunchException("Empty password can be used only with the root user");
82+
}
83+
setStartupAttempts(3);
84+
}
85+
86+
@Override
87+
public String getDriverClassName() {
88+
try {
89+
Class.forName("com.mysql.cj.jdbc.Driver");
90+
return "com.mysql.cj.jdbc.Driver";
91+
} catch (ClassNotFoundException e) {
92+
return "com.mysql.jdbc.Driver";
93+
}
94+
}
95+
96+
@Override
97+
public String getJdbcUrl() {
98+
String additionalUrlParams = constructUrlParameters("?", "&");
99+
return "jdbc:mysql://" + getHost() + ":" + getMappedPort(MYSQL_PORT) + "/" + databaseName + additionalUrlParams;
100+
}
101+
102+
@Override
103+
protected String constructUrlForConnection(String queryString) {
104+
String url = super.constructUrlForConnection(queryString);
105+
106+
if (!url.contains("useSSL=")) {
107+
String separator = url.contains("?") ? "&" : "?";
108+
url = url + separator + "useSSL=false";
109+
}
110+
111+
if (!url.contains("allowPublicKeyRetrieval=")) {
112+
url = url + "&allowPublicKeyRetrieval=true";
113+
}
114+
115+
return url;
116+
}
117+
118+
@Override
119+
public String getDatabaseName() {
120+
return databaseName;
121+
}
122+
123+
@Override
124+
public String getUsername() {
125+
return username;
126+
}
127+
128+
@Override
129+
public String getPassword() {
130+
return password;
131+
}
132+
133+
@Override
134+
public String getTestQueryString() {
135+
return "SELECT 1";
136+
}
137+
138+
public MySQLContainer withConfigurationOverride(String s) {
139+
parameters.put(MY_CNF_CONFIG_OVERRIDE_PARAM_NAME, s);
140+
return self();
141+
}
142+
143+
@Override
144+
public MySQLContainer withDatabaseName(final String databaseName) {
145+
this.databaseName = databaseName;
146+
return self();
147+
}
148+
149+
@Override
150+
public MySQLContainer withUsername(final String username) {
151+
this.username = username;
152+
return self();
153+
}
154+
155+
@Override
156+
public MySQLContainer withPassword(final String password) {
157+
this.password = password;
158+
return self();
159+
}
160+
}

modules/mysql/src/test/java/org/testcontainers/junit/mysql/CustomizableMysqlTest.java

Lines changed: 0 additions & 39 deletions
This file was deleted.

modules/mysql/src/test/java/org/testcontainers/junit/mysql/MultiVersionMySQLTest.java renamed to modules/mysql/src/test/java/org/testcontainers/mysql/MultiVersionMySQLTest.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
package org.testcontainers.junit.mysql;
1+
package org.testcontainers.mysql;
22

33
import org.junit.jupiter.params.ParameterizedTest;
44
import org.junit.jupiter.params.provider.MethodSource;
55
import org.testcontainers.MySQLTestImages;
6-
import org.testcontainers.containers.MySQLContainer;
76
import org.testcontainers.db.AbstractContainerDatabaseTest;
87
import org.testcontainers.utility.DockerImageName;
98

@@ -26,7 +25,7 @@ public static DockerImageName[] params() {
2625
@ParameterizedTest
2726
@MethodSource("params")
2827
void versionCheckTest(DockerImageName dockerImageName) throws SQLException {
29-
try (MySQLContainer<?> mysql = new MySQLContainer<>(dockerImageName)) {
28+
try (MySQLContainer mysql = new MySQLContainer(dockerImageName)) {
3029
mysql.start();
3130
final ResultSet resultSet = performQuery(mysql, "SELECT VERSION()");
3231
final String resultSetString = resultSet.getString(1);

modules/mysql/src/test/java/org/testcontainers/junit/mysql/SimpleMySQLTest.java renamed to modules/mysql/src/test/java/org/testcontainers/mysql/MySQLContainerTest.java

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
package org.testcontainers.junit.mysql;
1+
package org.testcontainers.mysql;
22

33
import org.junit.jupiter.api.Test;
44
import org.slf4j.Logger;
55
import org.slf4j.LoggerFactory;
66
import org.testcontainers.MySQLTestImages;
77
import org.testcontainers.containers.ContainerLaunchException;
8-
import org.testcontainers.containers.MySQLContainer;
98
import org.testcontainers.containers.output.Slf4jLogConsumer;
109
import org.testcontainers.db.AbstractContainerDatabaseTest;
1110

@@ -30,14 +29,14 @@
3029
import static org.assertj.core.api.Assertions.assertThatThrownBy;
3130
import static org.assertj.core.api.Assumptions.assumeThat;
3231

33-
class SimpleMySQLTest extends AbstractContainerDatabaseTest {
32+
class MySQLContainerTest extends AbstractContainerDatabaseTest {
3433

35-
private static final Logger logger = LoggerFactory.getLogger(SimpleMySQLTest.class);
34+
private static final Logger logger = LoggerFactory.getLogger(MySQLContainerTest.class);
3635

3736
@Test
3837
void testSimple() throws SQLException {
3938
try ( // container {
40-
MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0.36")
39+
MySQLContainer mysql = new MySQLContainer("mysql:8.0.36")
4140
// }
4241
) {
4342
mysql.start();
@@ -53,7 +52,7 @@ void testSimple() throws SQLException {
5352
@Test
5453
void testSpecificVersion() throws SQLException {
5554
try (
56-
MySQLContainer<?> mysqlOldVersion = new MySQLContainer<>(MySQLTestImages.MYSQL_80_IMAGE)
55+
MySQLContainer mysqlOldVersion = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)
5756
.withConfigurationOverride("somepath/mysql_conf_override")
5857
.withLogConsumer(new Slf4jLogConsumer(logger))
5958
) {
@@ -71,7 +70,7 @@ void testSpecificVersion() throws SQLException {
7170
@Test
7271
void testMySQLWithCustomIniFile() throws SQLException {
7372
try (
74-
MySQLContainer<?> mysqlCustomConfig = new MySQLContainer<>(MySQLTestImages.MYSQL_80_IMAGE)
73+
MySQLContainer mysqlCustomConfig = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)
7574
.withConfigurationOverride("somepath/mysql_conf_override")
7675
) {
7776
mysqlCustomConfig.start();
@@ -83,7 +82,7 @@ void testMySQLWithCustomIniFile() throws SQLException {
8382
@Test
8483
void testCommandOverride() throws SQLException {
8584
try (
86-
MySQLContainer<?> mysqlCustomConfig = new MySQLContainer<>(MySQLTestImages.MYSQL_80_IMAGE)
85+
MySQLContainer mysqlCustomConfig = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)
8786
.withCommand("mysqld --auto_increment_increment=42")
8887
) {
8988
mysqlCustomConfig.start();
@@ -98,7 +97,7 @@ void testCommandOverride() throws SQLException {
9897
@Test
9998
void testExplicitInitScript() throws SQLException {
10099
try (
101-
MySQLContainer<?> container = new MySQLContainer<>(MySQLTestImages.MYSQL_80_IMAGE)
100+
MySQLContainer container = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)
102101
.withInitScript("somepath/init_mysql.sql")
103102
.withLogConsumer(new Slf4jLogConsumer(logger))
104103
) {
@@ -114,7 +113,7 @@ void testExplicitInitScript() throws SQLException {
114113
@Test
115114
void testEmptyPasswordWithNonRootUser() {
116115
try (
117-
MySQLContainer<?> container = new MySQLContainer<>(MySQLTestImages.MYSQL_80_IMAGE)
116+
MySQLContainer container = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)
118117
.withDatabaseName("TEST")
119118
.withUsername("test")
120119
.withPassword("")
@@ -130,7 +129,7 @@ void testEmptyPasswordWithNonRootUser() {
130129
void testEmptyPasswordWithRootUser() throws SQLException {
131130
// Add MYSQL_ROOT_HOST environment so that we can root login from anywhere for testing purposes
132131
try (
133-
MySQLContainer<?> mysql = new MySQLContainer<>(MySQLTestImages.MYSQL_80_IMAGE)
132+
MySQLContainer mysql = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)
134133
.withDatabaseName("foo")
135134
.withUsername("root")
136135
.withPassword("")
@@ -147,7 +146,7 @@ void testEmptyPasswordWithRootUser() throws SQLException {
147146

148147
@Test
149148
void testWithAdditionalUrlParamTimeZone() throws SQLException {
150-
MySQLContainer<?> mysql = new MySQLContainer<>(MySQLTestImages.MYSQL_80_IMAGE)
149+
MySQLContainer mysql = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)
151150
.withUrlParam("serverTimezone", "Europe/Zurich")
152151
.withEnv("TZ", "Europe/Zurich")
153152
.withLogConsumer(new Slf4jLogConsumer(logger));
@@ -182,7 +181,7 @@ void testWithAdditionalUrlParamTimeZone() throws SQLException {
182181

183182
@Test
184183
void testWithAdditionalUrlParamMultiQueries() throws SQLException {
185-
MySQLContainer<?> mysql = new MySQLContainer<>(MySQLTestImages.MYSQL_80_IMAGE)
184+
MySQLContainer mysql = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)
186185
.withUrlParam("allowMultiQueries", "true")
187186
.withLogConsumer(new Slf4jLogConsumer(logger));
188187
mysql.start();
@@ -207,7 +206,7 @@ void testWithAdditionalUrlParamMultiQueries() throws SQLException {
207206

208207
@Test
209208
void testWithAdditionalUrlParamInJdbcUrl() {
210-
MySQLContainer<?> mysql = new MySQLContainer<>(MySQLTestImages.MYSQL_80_IMAGE)
209+
MySQLContainer mysql = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)
211210
.withUrlParam("allowMultiQueries", "true")
212211
.withUrlParam("rewriteBatchedStatements", "true")
213212
.withLogConsumer(new Slf4jLogConsumer(logger));
@@ -228,7 +227,7 @@ void testWithAdditionalUrlParamInJdbcUrl() {
228227
void testWithOnlyUserReadableCustomIniFile() throws Exception {
229228
assumeThat(FileSystems.getDefault().supportedFileAttributeViews().contains("posix")).isTrue();
230229
try (
231-
MySQLContainer<?> mysql = new MySQLContainer<>(MySQLTestImages.MYSQL_80_IMAGE)
230+
MySQLContainer mysql = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)
232231
.withConfigurationOverride("somepath/mysql_conf_override")
233232
.withLogConsumer(new Slf4jLogConsumer(logger))
234233
) {
@@ -252,12 +251,31 @@ void testWithOnlyUserReadableCustomIniFile() throws Exception {
252251
}
253252
}
254253

255-
private void assertHasCorrectExposedAndLivenessCheckPorts(MySQLContainer<?> mysql) {
254+
@Test
255+
void testCustom() throws SQLException {
256+
// Add MYSQL_ROOT_HOST environment so that we can root login from anywhere for testing purposes
257+
try (
258+
MySQLContainer mysql = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)
259+
.withDatabaseName("foo")
260+
.withUsername("bar")
261+
.withPassword("baz")
262+
.withEnv("MYSQL_ROOT_HOST", "%")
263+
) {
264+
mysql.start();
265+
266+
ResultSet resultSet = performQuery(mysql, "SELECT 1");
267+
268+
int resultSetInt = resultSet.getInt(1);
269+
assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1);
270+
}
271+
}
272+
273+
private void assertHasCorrectExposedAndLivenessCheckPorts(MySQLContainer mysql) {
256274
assertThat(mysql.getExposedPorts()).containsExactly(MySQLContainer.MYSQL_PORT);
257275
assertThat(mysql.getLivenessCheckPortNumbers()).containsExactly(mysql.getMappedPort(MySQLContainer.MYSQL_PORT));
258276
}
259277

260-
private void assertThatCustomIniFileWasUsed(MySQLContainer<?> mysql) throws SQLException {
278+
private void assertThatCustomIniFileWasUsed(MySQLContainer mysql) throws SQLException {
261279
try (ResultSet resultSet = performQuery(mysql, "SELECT @@GLOBAL.innodb_max_undo_log_size")) {
262280
long result = resultSet.getLong(1);
263281
assertThat(result)

0 commit comments

Comments
 (0)