【问题标题】:Returning mocked object from partially mocked object not working从部分模拟对象返回模拟对象不起作用
【发布时间】:2017-09-27 12:52:31
【问题描述】:

我正在编写一个单元测试,我试图在其中部分模拟一项服务。我的意思是我希望服务的一种方法返回一个不同的模拟对象,而另一种方法则表现正常。这是我正在测试的方法:

public async Task<List<string>> GetDeletedRecordIds<T>(DateTime startDate)
        where T : ISalesForceObject
    {
        List<string> result;
        try
        {
            var client = await this.GetForceClient();
            var init = await client.GetDeleted<DeletedRecordRootObject>(typeof(T).Name, startDate, DateTime.Now);
            result = init?.DeletedRecords.Select(d => d.Id).ToList();
        }
        catch (Exception e)
        {
            this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, "GetDeletedRecordIds");
            throw;
        }

        return result;
    }

这是我需要返回一个模拟对象的方法:

public async Task<IForceClient> GetForceClient()
    {
        ForceClient forceClient = null;
        try
        {
            var auth = new AuthenticationClient();

            var consumerKey = this._settingService.GetSetting("SalesForceConsumerKey");
            var consumerSecret = this._settingService.GetSetting("SalesForceConsumerSecret");
            var password = this._settingService.GetSetting("SalesForcePassword");
            var securityToken = this._settingService.GetSetting("SalesForceSecurityToken");
            var username = this._settingService.GetSetting("SalesForceUsername");
            var tokenUrl = $"{this._settingService.GetSetting("SalesForceUrl")}/services/oauth2/token";

            await auth.UsernamePasswordAsync(
                consumerKey,
                consumerSecret,
                username,
                password + securityToken,
                tokenUrl);

            forceClient = new ForceClient(auth.InstanceUrl, auth.AccessToken, auth.ApiVersion);
        }
        catch (Exception e)
        {
            this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, $"GetForceClient");
            throw;
        }

        return forceClient;
    }

这就是我目前在单元测试中的内容:

        var mockForceClient = new Mock<IForceClient>();
        mockForceClient
            .Setup(
                i => i.GetDeleted<DeletedRecordRootObject>(
                    It.IsAny<string>(),
                    It.IsAny<DateTime>(),
                    It.IsAny<DateTime>())).ReturnsAsync(deletedRecordRootObject);

        var mockService = new Mock<IForceDotComService>();
        mockService.Setup(m => m.GetDeletedRecordIds<sf.Account>(It.IsAny<DateTime>()))
            .Returns(async (DateTime d) => await this._service.GetDeletedRecordIds<sf.Account>(d));
        mockService.Setup(m => m.GetForceClient())
            .ReturnsAsync(mockForceClient.Object);

目前,测试在GetDeletedRecordIds 中运行,直到它调用GetForceClient 方法。然后,它实际上并没有返回模拟的 ForceClient 对象,而是尝试运行当然失败的方法。

提前感谢您的帮助。

解决方案: 以下是我解决问题的方法。

首先,我创建了一个服务来返回 ForceClient,如下所示:

public class ForceClientService : IForceClientService
{
    private readonly ILogger _logger;

    private readonly ISettingService _settingService;

    public ForceClientService(
        ILogger<ForceClientService> logger,
        ISettingService settingService)
    {
        this._logger = logger;
        this._settingService = settingService;
    }

    public async Task<IForceClient> GetForceClient()
    {
        ForceClient forceClient = null;
        try
        {
            var auth = new AuthenticationClient();

            var consumerKey = this._settingService.GetSetting("SalesForceConsumerKey");
            var consumerSecret = this._settingService.GetSetting("SalesForceConsumerSecret");
            var password = this._settingService.GetSetting("SalesForcePassword");
            var securityToken = this._settingService.GetSetting("SalesForceSecurityToken");
            var username = this._settingService.GetSetting("SalesForceUsername");
            var tokenUrl = $"{this._settingService.GetSetting("SalesForceUrl")}/services/oauth2/token";

            await auth.UsernamePasswordAsync(
                consumerKey,
                consumerSecret,
                username,
                password + securityToken,
                tokenUrl);

            forceClient = new ForceClient(auth.InstanceUrl, auth.AccessToken, auth.ApiVersion);
        }
        catch (Exception e)
        {
            this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, $"GetForceClient");
            throw;
        }

        return forceClient;
    }
}

然后我改变了我正在测试的方法:

public async Task DeleteRecord<TSf>(TSf record)
        where TSf : ISalesForceObject
    {
        try
        {
            var client = await this._forceClientService.GetForceClient();
            var response = await client.DeleteAsync(typeof(TSf).Name, record.Id);
            if (!response)
            {
                throw new Exception($"Error deleting record with ID {record.Id}");
            }
        }
        catch (Exception e)
        {
            this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, $"ForceDotComService.DeleteRecord");
            throw;
        }
    }

然后我重新构建了我的模拟来模拟依赖项与方法:

var mockForceClient = new Mock<IForceClient>();
        mockForceClient
            .Setup(
                i => i.GetDeleted<DeletedRecordRootObject>(
                    It.IsAny<string>(),
                    It.IsAny<DateTime>(),
                    It.IsAny<DateTime>())).ReturnsAsync(deletedRecordRootObject);

        var mockLogger = new Mock<ILogger<ForceDotComService>>();
        var mockForceClientService = new Mock<IForceClientService>();
        mockForceClientService.Setup(m => m.GetForceClient()).ReturnsAsync(mockForceClient.Object);

        this._service = new ForceDotComService(mockLogger.Object, mockForceClientService.Object);

它现在按预期工作。非常感谢您的帮助!

【问题讨论】:

  • 当然,你有new ForceClient() 在那里,所以它不使用你的模拟。注入该依赖项。话虽如此,您的问题中没有足够的相关代码来给出正确的答案,所以必须这样做。
  • 您不应该创建正在单元测试的类的模拟。此外,您不应该设置正在单元测试的类的方法。应该为此类所依赖的依赖项创建模拟。看起来您的课程依赖于_settingService。我认为您需要为各种参数创建_settingService 的模拟和设置方法GetSetting

标签: c# unit-testing asp.net-core moq


【解决方案1】:

this.GetForceClient() 提取到由抽象支持的自己的服务中

public IForceClientProvider {
    Task<IForceClient> GetForceClient();
}

然后,您将重构当前正在测试的类,以通过构造函数注入显式依赖该接口。

public class ForceDotComService : IForceDotComService {
    private readonly IForceClientProvider provider;

    public ForceDotComService(IForceClientProvider provider) {
        this.provider = provider;
    }

    public async Task<List<string>> GetDeletedRecordIds<T>(DateTime startDate)
        where T : ISalesForceObject {
        List<string> result;
        try {
            var client = await provider.GetForceClient();
            var init = await client.GetDeleted<DeletedRecordRootObject>(typeof(T).Name, startDate, DateTime.Now);
            result = init?.DeletedRecords.Select(d => d.Id).ToList();
        } catch (Exception e) {
            this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, "GetDeletedRecordIds");
            throw;
        }

        return result;
    }
}

这将允许您在测试时模拟所需的行为。在实现代码中,您将在GetForceClient() 方法中拥有与上述相同的代码。

【讨论】:

    【解决方案2】:

    你模拟依赖,而不是被测类的方法。

    您需要注入依赖项IForceClient,例如将其设为构造函数参数。因为现在您的GetForceClient() 只是在被测类上调用,该类在该类中而不是在您的模拟上运行,因此只需返回其中所述的new ForceClient()

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多