You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为基于MongoDB的Jobrunr存储配置编写单元测试

解决Jobrunr AmazonDocumentDBStorageProvider配置类单元测试Mock问题

问题背景

现有Spring配置类通过AmazonDocumentDBStorageProvider为Jobrunr提供存储支持,依赖MongoDB。编写单元测试以满足Sonar流水线95%覆盖率要求时,Mock MongoDB相关组件遇到空指针错误。

配置类代码

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.bson.UuidRepresentation;
import org.jobrunr.storage.StorageProvider;
import org.jobrunr.storage.nosql.documentdb.AmazonDocumentDBStorageProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig {

    @Value("${mongodb.host}")
    private String mongoHost;

    @Value("${mongodb.username}")
    private String username;

    @Value("${mongodb.password}")
    private String password;

    @Bean
    public StorageProvider getStorageProvider() {
        return getAmazonDocumentDB();
    }

    private AmazonDocumentDBStorageProvider getAmazonDocumentDB() {
        ConnectionString connectionString = new ConnectionString(getAmazonDocumentDbConnectionString());
        final MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
                .applyConnectionString(connectionString)
                .uuidRepresentation(UuidRepresentation.JAVA_LEGACY)
                .build();
        final MongoClient mongoClient = MongoClients.create(mongoClientSettings);
        return new AmazonDocumentDBStorageProvider(mongoClient);
    }

    private String getAmazonDocumentDbConnectionString() {
        final String template = "mongodb://%s:%s@%s/?replicaSet=rs0&readpreference=%s&retryWrites=false";
        final String username = this.username;
        final String password = this.password;
        final String host = this.mongoHost;
        final String readPreference = "secondaryPreferred";
        return String.format(template, username, password, host, readPreference);
    }
}

现有单元测试代码

@ExtendWith({MockitoExtension.class})
public class BeanConfigTest {

    @InjectMocks
    private BeanConfig beanConfig;

    @Value("${mongodb.host}")
    private String mongoHost = "localhost:27017";

    @Value("${mongodb.username}")
    private String username = "user";

    @Value("${mongodb.password}")
    private String password = "pass";

    private static MockedStatic<MongoClientSettings> mongoClientSettings = Mockito.mockStatic(MongoClientSettings.class);

    private static MockedStatic<MongoClients> mongoClients = Mockito.mockStatic(MongoClients.class);

    private static MongoClient mockMongoClient = Mockito.mock(MongoClient.class);

    @InjectMocks
    private ActionHandler actionHandler;

    @Mock
    private StorageService storageService;

    @BeforeAll
    public static void init(){
        mongoClients.when(() -> MongoClients.create(any(MongoClientSettings.class))).thenReturn(mockMongoClient);
    }


    @Test
    public void testActionHandler() {
        StorageProvider storageProvider = beanConfig.getStorageProvider();
        assertNotNull(storageProvider);
    }
}

报错信息

java.lang.NullPointerException: Cannot invoke "com.mongodb.client.MongoDatabase.getCollection(String)" because "this.jobrunrDatabase" is null

    at org.jobrunr.storage.nosql.mongo.MongoDBCreator.<init>(MongoDBCreator.java:49)
    at org.jobrunr.storage.nosql.mongo.MongoDBCreator.<init>(MongoDBCreator.java:42)
    at org.jobrunr.storage.nosql.documentdb.AmazonDocumentDBStorageProvider.runMigrations(AmazonDocumentDBStorageProvider.java:59)
    at org.jobrunr.storage.nosql.mongo.MongoDBStorageProvider.setUpStorageProvider(MongoDBStorageProvider.java:145)
    at org.jobrunr.storage.nosql.mongo.MongoDBStorageProvider.<init>(MongoDBStorageProvider.java:126)
    at org.jobrunr.storage.nosql.mongo.MongoDBStorageProvider.<init>(MongoDBStorageProvider.java:111)
    at org.jobrunr.storage.nosql.mongo.MongoDBStorageProvider.<init>(MongoDBStorageProvider.java:91)
    at org.jobrunr.storage.nosql.documentdb.AmazonDocumentDBStorageProvider.<init>(AmazonDocumentDBStorageProvider.java:26)
    at com.cs.config.BeanConfig.getAmazonDocumentDB(BeanConfig.java:39)
    at com.cs.config.BeanConfig.getStorageProvider(BeanConfig.java:28)
    at com.cs.config.BeanConfigTest.testActionHandler(BeanConfigTest.java:87)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

解决方案

问题分析

  1. @Value注入未生效:MockitoExtension不会自动处理@Value注解,导致BeanConfig的私有字段为null,生成连接字符串时会出错。
  2. MongoClient Mock不完整:AmazonDocumentDBStorageProvider初始化时会调用MongoClient.getDatabase()获取Jobrunr数据库,未Mock该方法导致空指针。
  3. 不必要的静态Mock:MongoClientSettings不需要MockStatic,正常创建实例即可。

修正后的测试代码

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;

@ExtendWith(MockitoExtension.class)
public class BeanConfigTest {

    @InjectMocks
    private BeanConfig beanConfig;

    @Mock
    private MongoClient mockMongoClient;
    @Mock
    private MongoDatabase mockMongoDatabase;
    @Mock
    private MongoCollection mockMongoCollection;

    private static MockedStatic<MongoClients> mongoClientsStatic;

    @BeforeAll
    public static void initStaticMocks() {
        mongoClientsStatic = Mockito.mockStatic(MongoClients.class);
    }

    @AfterAll
    public static void closeStaticMocks() {
        mongoClientsStatic.close();
    }

    @BeforeEach
    public void setUp() {
        // 手动设置BeanConfig的@Value私有字段
        ReflectionTestUtils.setField(beanConfig, "mongoHost", "localhost:27017");
        ReflectionTestUtils.setField(beanConfig, "username", "user");
        ReflectionTestUtils.setField(beanConfig, "password", "pass");

        // 配置MongoClient完整调用链的Mock行为
        mongoClientsStatic.when(() -> MongoClients.create(any(MongoClientSettings.class))).thenReturn(mockMongoClient);
        Mockito.when(mockMongoClient.getDatabase(eq("jobrunr"))).thenReturn(mockMongoDatabase);
        Mockito.when(mockMongoDatabase.getCollection(any(String.class))).thenReturn(mockMongoCollection);
    }

    @Test
    public void testGetStorageProvider() {
        StorageProvider storageProvider = beanConfig.getStorageProvider();
        assertNotNull(storageProvider);
    }

    @Test
    public void testGetAmazonDocumentDbConnectionString() {
        // 调用私有方法验证连接字符串生成逻辑
        String connectionString = (String) ReflectionTestUtils.invokeMethod(beanConfig, "getAmazonDocumentDbConnectionString");
        assertNotNull(connectionString);
        String expected = "mongodb://user:pass@localhost:27017/?replicaSet=rs0&readpreference=secondaryPreferred&retryWrites=false";
        assertEquals(expected, connectionString);
    }
}

关键改进点

  • 使用ReflectionTestUtils手动设置BeanConfig的私有字段,解决@Value注入不生效的问题。
  • 完整Mock MongoClient的调用链:MongoClients.create() → MongoClient.getDatabase() → MongoDatabase.getCollection(),覆盖Jobrunr初始化时的数据库操作,避免空指针。
  • 静态Mock资源在@BeforeAll初始化,@AfterAll销毁,防止测试资源泄漏。
  • 新增对私有方法getAmazonDocumentDbConnectionString()的测试,提升代码覆盖率。

内容的提问来源于stack exchange,提问作者Sachin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 14:17:04