2017-09-27 168 views
1

我正在编写一个单元测试,我试图部分模拟服务。我的意思是我希望服务的某个方法返回一个不同的模拟对象,并让另一个方法像平常一样运行。这是我测试的方法:从部分模拟对象返回模拟对象不工作

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对象,而是实际尝试运行当然失败的方法。

在此先感谢您的帮助。

SOLUTION: 以下是我如何解决我的问题。

首先,我创建了一个服务来回报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; 
     } 
    } 

然后我重建模拟嘲笑依赖VS方法:

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); 

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

+1

当然,你有'新的ForceClient()'在那里,所以它不使用你的模拟。注入该依赖关系。这就是说,你的问题中没有足够的相关代码来给出正确的答案,所以这将不得不做。 – CodeCaster

+0

您不应该为正在进行单元测试的类创建模拟。你也不应该设置被单元测试的类的方法。 Mocks应该创建这个类所依赖的依赖关系。看起来你的班级依赖于'_settingService'。我认为你需要为各种参数创建'_settingService'的模拟和'GetSetting'的设置方法。 –

回答

2

提取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()