【问题标题】:FakeItEasy - How to verify nested arguments value C#FakeItEasy - 如何验证嵌套参数值 C#
【发布时间】:2017-08-10 21:04:28
【问题描述】:

我需要您的帮助,以便找到一种方法来验证作为被测方法调用的参数传递的嵌套对象的值。 假设这个类:

public class AuditTrailValueObject
{
    public ActionType Action { get; private set; }
    public EntityType EntityType { get; private set; }
    public long EntityId { get; private set; }
    public DateTime StartTime { get; private set; }
    public bool IsSuccess { get; private set; }
    public string Remarks { get; private set; }

    public AuditTrailValueObject(ActionType action, EntityType entityType, long entityId, DateTime startTime, bool isSuccess, string remarks = "")
    {
        Action = action;
        EntityType = entityType;
        EntityId = entityId;
        StartTime = startTime;
        IsSuccess = isSuccess;
        Remarks = remarks;
    }
}

并且以下接口将此类作为注入依赖项:

public interface IAuditTrailService
{
    void WriteToAuditTrail(AuditTrailValueObject auditParamData);
}

现在我有了ScanService,具体取决于AuditTrailService(实现IAuditTrailService):

public long CreateScanRequest(long projectId)
{
    ScanRequestWriteModel scanRequest = _scanRequestWriteModelFactory.Create(projectDetails);

    long scanRequestId = _scanRequestsWriteRepository.Insert(scanRequest);

    _auditTrailService.WriteToAuditTrail(new AuditTrailValueObject(ActionType.Run, EntityType.SastScanRequest, scanRequestId, DateTime.UtcNow, true));

    return scanRequestId;
}

我写的测试:

[TestMethod]
public void Scan_GivenProjectId_ShouldAuditSuccess()
{
    //Given
    var projectId = 100;

    var scanService = CreateScanService();

    ...
    A.CallTo(() => _scanRequestWriteModelFactory.Create(projectDetails)).Returns(new ScanRequestWriteModel());
    A.CallTo(() => _scanRequestsWriteRepository.Insert(A<ScanRequestWriteModel>._)).Returns(1);

    //When
    var scanRequestId = scanService.CreateScanRequest(projectId);

    //Then
     A.CallTo(() => _auditTrailService.WriteToAuditTrail(
                        new AuditTrailValueObject(ActionType.Run, EntityType.SastScanRequest, scanRequestId, A<DateTime>._, true, A<string>._))).MustHaveHappened();
}

运行此测试时,我得到:

System.InvalidCastException:指定的强制转换无效

如何验证AuditTrailValueObject 中嵌套参数的值?

【问题讨论】:

  • 你能告诉我们错误发生在哪里吗?你有堆栈跟踪吗?有什么可以缩小范围吗?你已经在这个问题中倾倒了大量的代码。或许可以咨询stackoverflow.com/help/how-to-ask 并重新表述一下这个问题。我确实拿走了你所有的代码并尝试重现。我遇到了一些问题,因为遗漏了很多位,我不得不做出猜测。您的测试也无法编译:我认为您有重复的“A.CallTo(() => _auditTrailService.WriteToAuditTrail(”行。如果您纠正这些问题并提供完整的测试输出,我会再试一次。
  • @BlairConrad 我已经修正了你注意到的错误线路。要不要我上传所有错过的台词(可能会更累...)?
  • 我认为最好指出无效演员表的来源。如果您正在捕获异常,请包含异常,包括整个堆栈跟踪。 (考虑到您已经包含的代码量,更多并没有那么糟糕。我会说要么减少多余的代码,直到你有一个非常集中的示例,要么包含足够的代码来编译和运行。)
  • @BlairConrad 我知道问题出在A.CallTo(() =&gt; _auditTrailService.WriteToAuditTrail( **new** AuditTrailValueObject(正如@tomredffern 解释的那样)。我正在寻找另一种方法来验证 scanService 传递给 writeToAuditTrail 方法的值。

标签: unit-testing fakeiteasy


【解决方案1】:

@tom redfern 提出了很多好的观点,您可能想要解决这些问题。但是在重读你的代码和 cmets 之后,我认为我是一个直接的前进方向。您的代码至少有一个问题,而且可能还有另一个问题。

我们来看看

A.CallTo(() => _auditTrailService.WriteToAuditTrail(
                        new AuditTrailValueObject(ActionType.Run,
                                                  EntityType.SastScanRequest,
                                                  scanRequestId,
                                                  A<DateTime>._,
                                                  true
                                                  A<string>._)))
        .MustHaveHappened();

_ 构造在 AuditTrailValueObject 构造函数中使用,它们在那里无效。它们将导致将默认值分配给 AuditTrailValueObject(我认为DateTime.MinValue 和 null),并且几乎不是您想要的。如果您将new 提取到上一行,您将看到FakeItEasy 在使用_ 时抛出错误。我认为它应该更好地帮助您找到代码中的问题,但我不确定这是否可能。我创建了FakeItEasy Issue 1177 - Argument constraint That, when nested deeper in A.CallTo, misreports what's being matched 来帮助 FakeItEasy 改进。

与此相关的是 FakeItEasy 如何匹配对象。当提供要比较的值时,(new AuditTrailValueObject(…) 的结果)FakeItEasy 将使用Equals 将对象与接收到的参数进行比较。除非您的 AuditTrailValueObject 有一个好的 Equals,否则这将失败。

如果您想继续使用 AuditTrailValueObject 并且不想提供 Equals(这会忽略 startTime 和备注),有办法。

首先,您可以使用That.Matches,如下所示:

A.CallTo(() => _auditTrailService.WriteToAuditTrail(A<AuditTrailValueObject>.That.Matches(
                                                        a => a.Action == ActionType.Run &&
                                                        a.EntityType == EntityType.SastScanRequest &&
                                                        a.EntityId == scanRequestId &&
                                                        a.IsSuccess)))
                                                    .MustHaveHappened();

有些人不喜欢 Matches 中的复杂约束,因此另一种方法是捕获 AuditTrailValueObject 并稍后对其进行询问,正如 Alex James Brown 在他对 Why can't I capture a FakeItEasy expectation in a variable? 的回答中所描述的那样。

【讨论】:

    【解决方案2】:

    您的问题是一个更大问题的征兆:您试图通过一项测试做太多事情。

    因为您在 WriteToAuditTrail() 方法中新建了一个 AuditTrailValueObject 实例,所以您将无法访问此对象实例,因为它是在方法范围内创建的,因此不受检查。

    但是,您首先希望访问此对象的唯一原因似乎是您可以验证其中设置的值是否正确。

    在这些值中,只有一个(据您的代码示例允许我们知道)是从调用方法中设置的。这是对 _scanRequestsWriteRepository.Insert() 的调用的返回值,它应该是其自己的单元测试的主题,您可以在其中验证正确的行为,而与使用它的位置无关。

    编写此单元测试(在 _scanRequestsWriteRepository.Insert() 方法上)实际上将解决问题的根本原因(您对单个测试做了太多事情)。但是,您的直接问题仍然需要解决。执行此操作的最简单方法是完全删除 AuditTrailValueObject 类,并将您的参数直接传递给对 WriteToAuditTrail() 的调用。

    如果我要删除 AuditTrailValueObject 我应该验证的地方 哪些参数被传递给 auditTrailService?我的意思是 如果我测试了 auditTrailService 我也需要知道 如果使用正确的参数(例如:使用 ActionType.Run 而不是 ActionType.Update)。

    要验证是否已将正确的参数传递给对 WriteToAuditTrail() 的调用,您可以注入一个假的 IAudiTrailService 并验证您的调用是否发生:

    A.CallTo(
        () => _auditTrailService.WriteToAuditTrail(
                        ActionType.Run, 
                        EntityType.SastScanRequest, 
                        scanRequestId, 
                        myDateTime, 
                        true, 
                        myString)
    ).MustHaveHappened();
    

    【讨论】:

    • @tomredffern 从概念上讲,我同意你的看法。我不确定我完全理解你的意思。我要验证的值是AuditTrailValueObject 字段,例如ActionType.RunEntityType.SastScanRequest。这些值不是来自_scanRequestsWriteRepository.Insert() 调用的返回对象的一部分。所以我的问题是,如果我不使用您建议的更改,我该如何验证这些值?
    • @Roni - 我知道您要验证哪些值。但是,我是说这里不是进行此类验证的地方。验证这些值的地方是在它们被创建的时候,而不是在它们被使用的时候。此外,我是说您可以更进一步,完全删除 AuditTrailValueObject 对象。我在代码中看不到它存在的原因。你不需要它——它除了存储值之外什么都不做。
    • 如果我要删除AuditTrailValueObject,我应该在哪里验证哪些参数被传递给auditTrailService?我的意思是,如果我测试了auditTrailService,我需要知道扫描服务调用是否使用正确的参数(例如:使用ActionType.Run 而不是ActionType.Update)。谢谢!
    • @Roni - 要验证是否已将正确的参数传递给对 WriteToAuditTrail() 的调用,您可以注入一个假的 IAudiTrailService 并验证您的调用是否发生。查看我的答案的更新
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多