在 Spring Boot 中使用嵌入式 MongoDB 测试 @Transactional [英] Test @Transactional with Flapdoodle Embedded MongoDB in Spring Boot
问题描述
我想确保@Transactional 注释有效,所以我编写了一个保存和发布文章的测试——我的 kafka 发布者是一个模拟,它在任何调用时抛出异常.我想确保 MongoDB 回滚持久化的文章.
I want to ensure that @Transactional annotation works so I wrote a test which save and publish article - my kafka publisher is a mock which throws an exception on any call. I want to ensure MongoDB rolls back the persisted article.
@Test
void testRollbackOnPublishFail() {
when(producer.publishArticle(any())).thenThrow(IllegalStateException.class);
ArticleDocument articleDocument = ArticleTestDataUtil.createArticleDocument();
try {
ArticleDocument publishedDocument = articleService.saveAndPublish(articleDocument);
} catch (Exception e) {
assertTrue(e instanceof IllegalStateException);
}
assertFalse(articleService.findById(articleDocument.getId()).isPresent());
}
我正在使用嵌入式 mongo db 进行集成测试
I am using flapdoodles embedded mongo db for integration tests
testCompile "de.flapdoodle.embed:de.flapdoodle.embed.mongo:2.2.0"
此测试失败,因为默认情况下没有事务/复制.
This tests fails because there is no transaction / replication on default.
通过创建 MongoTransactionManager 激活交易:
So activated transactions by creating MongoTransactionManager:
@Configuration
public class MongoTransactionConfig {
@Bean
public MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
}
现在我的测试失败了,因为无法在 MongoClient 中启动会话
Now my test fails because was not able to start a Session in MongoClient
com.mongodb.MongoClientException: Sessions are not supported by the MongoDB cluster to which this client is connected
at com.mongodb.MongoClient.startSession(MongoClient.java:560)
我也尝试创建一个自定义的 IMongodConfig
I also tried to create a custom IMongodConfig
@Bean(name = "customReplicaMongodConfig")
public IMongodConfig mongodConfig(EmbeddedMongoProperties embeddedProperties) throws IOException {
Storage storage = new Storage("/tmp", "rs0", 0);
return new MongodConfigBuilder()
.shardServer(true)
.version(Version.V4_0_2)
.net(new Net(27117, Network.localhostIsIPv6()))
.replication(storage)
.cmdOptions(new MongoCmdOptionsBuilder().useNoJournal(false).build()).build();
}
并启动复制:
@ConditionalOnBean(name = "customReplicaMongodConfig")
@Configuration
public class ReplicaConfig {
@Inject
private MongoClient mongoClient;
@PostConstruct
public void initiateReplicationSet() {
mongoClient.getDatabase("admin").runCommand(new Document("replSetInitiate", new Document()));
}
}
但是 replSetInitiate 因超时而失败.
But the replSetInitiate failed with an timeout.
所以我的问题是是否有可能创建一个带有嵌入式 MongoDB 的运行复制集来测试事务性.
So my question is if it is possible to create a running replication set with embedded MongoDB to test transactional.
推荐答案
您可以找到有关创建副本集的信息 这里
You can find information about creating replica set here
我的 Kotlin 解决方案:
My Kotlin solution:
import com.mongodb.BasicDBList
import com.mongodb.BasicDBObjectBuilder
import com.mongodb.DBObject
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import de.flapdoodle.embed.mongo.MongodExecutable
import de.flapdoodle.embed.mongo.MongodProcess
import de.flapdoodle.embed.mongo.MongodStarter
import de.flapdoodle.embed.mongo.config.MongoCmdOptionsBuilder
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder
import de.flapdoodle.embed.mongo.config.Net
import de.flapdoodle.embed.mongo.distribution.Version
import de.flapdoodle.embed.process.runtime.Network
import org.assertj.core.api.Assertions.assertThat
import org.bson.Document
import org.junit.jupiter.api.Test
import org.springframework.data.mongodb.MongoDatabaseFactory
import org.springframework.data.mongodb.MongoTransactionManager
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory
import org.springframework.test.context.ActiveProfiles
import org.springframework.transaction.TransactionStatus
import org.springframework.transaction.support.TransactionCallbackWithoutResult
import org.springframework.transaction.support.TransactionTemplate
import java.io.IOException
@ActiveProfiles("test")
class EmbeddedMongoDbTransactionTest {
private val CONNECTION_STRING = "mongodb://%s:%d/"
private var node1MongodExe: MongodExecutable? = null
private var node1Mongod: MongodProcess? = null
private var mongo: MongoClient? = null
private var node2MongodExe: MongodExecutable? = null
private var node2Mongod: MongodProcess? = null
@Test
@Throws(IOException::class)
fun testSmth() {
val runtime = MongodStarter.getDefaultInstance()
val node1Port = 57023
val node2Port = 57024
try {
node1MongodExe = runtime.prepare(
MongodConfigBuilder().version(Version.Main.PRODUCTION)
.withLaunchArgument("--replSet", "rs0")
.cmdOptions(MongoCmdOptionsBuilder().useNoJournal(false).build())
.net(Net(node1Port, Network.localhostIsIPv6())).build()
)
node1Mongod = node1MongodExe?.start()
node2MongodExe = runtime.prepare(
MongodConfigBuilder().version(Version.Main.PRODUCTION)
.withLaunchArgument("--replSet", "rs0")
.cmdOptions(MongoCmdOptionsBuilder().useNoJournal(false).build())
.net(Net(node2Port, Network.localhostIsIPv6())).build()
)
node2Mongod = node2MongodExe?.start()
mongo = MongoClients.create(CONNECTION_STRING.format("localhost", node1Port))
val adminDatabase: MongoDatabase = mongo!!.getDatabase("admin")
val config = Document("_id", "rs0")
val members = BasicDBList()
members.add(Document("_id", 0).append("host", "localhost:$node1Port"))
members.add(Document("_id", 1).append("host", "localhost:$node2Port"))
config.put("members", members)
adminDatabase.runCommand(Document("replSetInitiate", config))
println(">>>>>> wait")
println(">>>>>>>>" + adminDatabase.runCommand(Document("replSetGetStatus", 1)))
Thread.sleep(15_000) // without waiting fails with error : 'not master' on server
val funDb: MongoDatabase = mongo?.getDatabase("fun")!!
// insert test 1
val testCollection: MongoCollection<Document> = funDb.getCollection("test")
println(">>>>>>>> inserting data")
testCollection.insertOne(Document("fancy", "value"))
println(">>>>>>>> finding data")
assertThat(testCollection.find().first()!!.get("fancy")).isEqualTo("value")
// insert test 2 (with transaction)
val mongoTemplate = MongoTemplate(mongo!!, "test")
// Without creating collection in advance fails with error:
// Cannot create namespace in multi-document transaction
// (https://stackoverflow.com/questions/52585715/cannot-create-namespace-in-multi-document-transactionmongodb-4-0-spring-data-2)
mongoTemplate.createCollection("collection")
val mongoDatabaseFactory: MongoDatabaseFactory = SimpleMongoClientDatabaseFactory(mongo!!, "test")
val mongoTransactionManager = MongoTransactionManager(mongoDatabaseFactory)
val transactionTemplate = TransactionTemplate(mongoTransactionManager)
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
override fun doInTransactionWithoutResult(status: TransactionStatus) {
val objectToSave = BasicDBObjectBuilder.start()
.add("key", "value")
.get()
// when
mongoTemplate.save(objectToSave, "collection")
// then
assertThat(mongoTemplate.findAll(DBObject::class.java, "collection"))
.extracting("key")
.containsOnly("value")
}
})
// after transaction
assertThat(mongoTemplate.findAll(DBObject::class.java, "collection"))
.extracting("key")
.containsOnly("value")
} finally {
println(">>>>>> shutting down")
mongo?.close()
node1MongodExe?.stop()
node1Mongod?.stop()
node2MongodExe?.stop()
node2Mongod?.stop()
}
}
}
这篇关于在 Spring Boot 中使用嵌入式 MongoDB 测试 @Transactional的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!