spring - 在 Spring Boot 中使用嵌入式 MongoDB 测试 @Transactional

标签 spring mongodb spring-boot

我想确保 @Transactional 注释有效,所以我编写了一个保存和发布文章的测试 - 我的 kafka 发布者是一个模拟,它在任何调用时抛出异常。我想确保 MongoDB 回滚持久化的文章。

@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 进行集成测试
 testCompile "de.flapdoodle.embed:de.flapdoodle.embed.mongo:2.2.0"

此测试失败,因为默认情况下没有事务/复制。

因此通过创建 MongoTransactionManager 激活事务:
@Configuration
public class MongoTransactionConfig {

    @Bean
    public MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
        return new MongoTransactionManager(dbFactory);
    }
}

现在我的测试失败了,因为无法在 MongoClient 中启动 session
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
@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 因超时而失败。

所以我的问题是是否可以创建一个带有嵌入式 MongoDB 的运行复制集来测试事务。

最佳答案

您可以找到有关创建副本集的信息 here
我的 Kotlin 解决方案:

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 - 在 Spring Boot 中使用嵌入式 MongoDB 测试 @Transactional,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61885466/

相关文章:

java - Java 中的 MongoDB 文档更新 : coding error or driver bug?

PHP/MongoDB : update a value in an array

mysql - 将 RDBMS 与 Apache Directory Ldap 同步

Java Spring Security - User.withDefaultPasswordEncoder() 已弃用?

java - 无法模拟 FileInputStream

Spring MVC Thymeleaf Kotlin

spring - 定义多个数据源时出现异常 'java.lang.IllegalStateException:Expected one datasource and found 2'

mongodb - 将 mgo 与转换为 map[string]interface{} 的嵌套文档一起使用。如何遍历 map ?

java - spring boot后端+前端的热重载

java - Spring Cache——无法获取数据列表