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

Azure Tables/Blobs测试遇final类无法Mock及Autowired空值问题

解决Azure Tables/Blobs客户端无法Mock及Autowired注入空值问题

一、Final类无法Mock的解决方案

Azure SDK中的TableServiceClient、TableClient(包括Blob相关类)都是final类,默认Mockito无法直接Mock,这里提供两种实用方案:

方案1:启用Mockito对Final类的支持

Mockito 2.10+版本支持Mock final类,只需两步配置:

  1. 在测试资源路径下创建文件mockito-extensions/org.mockito.plugins.MockMaker,写入内容:mock-maker-inline
  2. 正常使用Mockito创建Mock实例:
@Mock
private TableServiceClient mockTableServiceClient;

@Mock
private TableClient mockTableClient;

测试时指定Mock行为,模拟表客户端获取场景:

when(mockTableServiceClient.getTableClient("Preferences")).thenReturn(mockTableClient);

方案2:封装自定义接口做依赖反转

如果不想依赖Mockito的特殊配置,推荐自己封装一层操作接口,让业务类依赖自定义接口而非SDK的final类:

// 自定义表操作接口
public interface AzureTableOperations {
    TableEntity getEntity(String partitionKey, String rowKey);
    void upsertEntity(TableEntity entity);
}

// 接口实现类,封装Azure SDK逻辑
@Service
public class AzureTableOperationsImpl implements AzureTableOperations {
    private final TableClient tableClient;

    public AzureTableOperationsImpl(TableServiceClient tableServiceClient) {
        this.tableClient = tableServiceClient.getTableClient("Preferences");
    }

    @Override
    public TableEntity getEntity(String partitionKey, String rowKey) {
        try {
            return tableClient.getEntity(partitionKey, rowKey);
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public void upsertEntity(TableEntity entity) {
        tableClient.upsertEntity(entity);
    }
}

修改业务类,依赖自定义接口:

@Service
public class AzureServicesTables implements PreferencesPersistenceService {
    private final AzureTableOperations tableOperations;
    private final String PARTITION_KEY = "PartitionKey";

    @Autowired
    public AzureServicesTables(AzureTableOperations tableOperations) {
        this.tableOperations = tableOperations;
    }

    public String getUserPreferencesField(String user) {
        TableEntity entity = tableOperations.getEntity(PARTITION_KEY, user);
        if (entity == null) {
            entity = tableOperations.getEntity(PARTITION_KEY, "default");
        }
        return String.valueOf(entity.getProperties().get("value"));
    }

    // savePreferences等方法同理修改,直接调用tableOperations方法
}

这样测试时可以轻松Mock自定义接口,完全避开final类限制。

二、Autowired注入空值的解决方法

测试时注入空值通常是因为没有正确处理依赖加载,分两种场景处理:

  1. 单元测试:不要用Autowired,手动构造业务类并传入Mock依赖:
@ExtendWith(MockitoExtension.class)
public class AzureServicesTablesTest {
    @Mock
    private TableServiceClient mockTableServiceClient;

    @Mock
    private TableClient mockTableClient;

    private AzureServicesTables service;

    @BeforeEach
    void setUp() {
        service = new AzureServicesTables(mockTableServiceClient);
        when(mockTableServiceClient.getTableClient("Preferences")).thenReturn(mockTableClient);
    }

    // 测试逻辑...
}
  1. 集成测试:添加@SpringBootTest加载上下文,用@MockBean替换真实依赖为Mock实例:
@SpringBootTest
public class AzureServicesTablesIntegrationTest {
    @MockBean
    private TableServiceClient mockTableServiceClient;

    @Autowired
    private AzureServicesTables service;

    @BeforeEach
    void setUp() {
        TableClient mockTableClient = Mockito.mock(TableClient.class);
        when(mockTableServiceClient.getTableClient("Preferences")).thenReturn(mockTableClient);
        // 配置mockTableClient的行为...
    }

    // 测试逻辑...
}

三、模拟已创建表的测试示例

下面是单元测试的完整示例,模拟用户不存在时返回默认配置的场景:

@ExtendWith(MockitoExtension.class)
public class AzureServicesTablesTest {
    @Mock
    private TableServiceClient mockTableServiceClient;

    @Mock
    private TableClient mockTableClient;

    private AzureServicesTables service;

    @BeforeEach
    void setUp() {
        service = new AzureServicesTables(mockTableServiceClient);
        when(mockTableServiceClient.getTableClient("Preferences")).thenReturn(mockTableClient);
    }

    @Test
    void getUserPreferencesField_UserNotFound_ReturnsDefault() {
        // 模拟用户查询抛出异常
        when(mockTableClient.getEntity("PartitionKey", "testUser")).thenThrow(new ResourceNotFoundException("Not found"));
        // 模拟默认配置存在
        TableEntity defaultEntity = new TableEntity("PartitionKey", "default");
        defaultEntity.addProperty("value", "{\"theme\":\"dark\"}");
        when(mockTableClient.getEntity("PartitionKey", "default")).thenReturn(defaultEntity);

        String result = service.getUserPreferencesField("testUser");

        assertEquals("{\"theme\":\"dark\"}", result);
        verify(mockTableClient, times(1)).getEntity("PartitionKey", "testUser");
        verify(mockTableClient, times(1)).getEntity("PartitionKey", "default");
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 23:26:02