# 三、FakeItEasy 简介

在开始编写单元测试和使用 FakeItEasy 之前，让我们先对框架有一个扎实的了解。本章将重点介绍什么是可以伪造的，如何创建不同的伪造，如何向伪造馈送构造函数参数，哪些成员可以被 FakeItEasy 覆盖，以及创建伪造时的默认行为“开箱即用”。

## 什么可以造假？

### 接口

由于传递到类中的许多依赖项都是通过接口完成的，所以大多数时候，您会为接口创建一个伪依赖项。

### 类

有时候你想伪造一个类的功能。类可以作为依赖项注入到其他类中(尽管首选方法是使用接口)。这些是可以伪造的类的类型:

*   未密封的类
*   非静态的类
*   至少有一个其参数 FakeItEasy 可以构造或获取的公共或受保护构造函数的类。

如果我们试图伪造一个静态类，我们将会看到:

![](img/image016.png)

图 13:你不能伪造一个静态类

请注意，编译器已经向我们显示了一个错误。如果您将鼠标悬停在带红色下划线的类名上，您将从编译器中看到以下错误:

![](img/image017.png)

图 14:编译器不会让你伪造一个静态类

如果我们试图伪造一个密封类，不像静态类，编译器不会给我们一个错误，并允许代码编译。但是，单元测试仍然会失败。下面是一个静态类的类和测试:

```cs
    public sealed class YouCannotFakeASealedClass
    {
        public void DoSomething()
        {
            //some implementation
        }
    }

    [TestFixture]
    public class WhenTryingToFakeASealedClass
    {
        private YouCannotFakeASealedClass sut;

        [SetUp]
        public void Given()
        {
            sut = A.Fake<YouCannotFakeASealedClass>();
            sut.DoSomething();
        }

        [Test]
        public void ShouldNotDoAnything()
        {
            A.CallTo(() => sut.DoSomething()).MustHaveHappened(Repeated.Exactly.Once);
        }
    }

```

代码清单 15:密封类的单元测试

下面是单元测试失败的例子:

![](img/image018.png)

图 15:试图伪造密封类的单元测试失败

### with argumentersfor 构造函数

以下是如何使用`WithArgumentsForConstructor` `:`将参数传递给你的赝品

```cs
    public class AClassWithArguments
    {
        private readonly string thisIsAnArgument;

        public AClassWithArguments(string thisIsAnArgument)
        {
            this.thisIsAnArgument = thisIsAnArgument;
        }

        public virtual void AFakeableMethod()
        {
            Console.Write(thisIsAnArgument);
        }
    }

    [TestFixture]
    public class WhenFakingAClassWithArgumentsUsingWithArgumentsForConstructor
    {
        private AClassWithArguments sut;

        [SetUp]
        public void Given()
        {
            sut = A.Fake<AClassWithArguments>(x => x.WithArgumentsForConstructor(
            () => new AClassWithArguments(A<string>.Ignored)));
            sut.AFakeableMethod();
        }

        [Test]
        public void ACallToAFakeableMethodMustHaveHappened()
        {
            A.CallTo(() => sut.AFakeableMethod()).MustHaveHappened();
        }
    }

```

代码清单 16:向伪造的 SUT 传递参数

注意我们如何使用`A.Fake<T>`使用类来构造赝品，但是我们的 lambda 使用`WithArgumentsForConstructor`以便让 FakeItEasy 在传递给赝品的构造函数的值中获得一个“钩子”。

| ![](img/note.png) | 注意:不要担心`A.string<Ignored>`或者肯定已经发生了。我们将在本书的后面讨论将值传递给方法。现在，知道传递一个<弦>。被忽略到 fake 的方法中意味着我们要么不关心这个值，要么不反对这个被忽略的值可能影响我们的单元测试的任何事情。MustHaveHappened 是一种断言在配置的伪造中已经发生或没有发生的事情的方法。WithArgumentsForConstructor 将在第 9 章“伪造 Sut”中深入讨论 |

## 哪些成员可以被覆盖？

*   虚拟成员
*   抽象成员
*   伪造接口时的接口方法

请注意，此列表中缺少静态成员；它们不可推翻。

## 默认假冒行为

这是我们在创建赝品时“开箱即用”的行为。在本书的后面，我们将从第 5 章“配置对赝品的调用”开始，了解更多关于赝品的行为现在，下面两个部分将概述当你简单地创建一个新的赝品时你得到的行为。

### 不可重写成员不能伪造

当我们试图在一个类上伪造一个不可重写的方法时会发生什么？给定以下类:

```cs
    public class AClassWithAnUnfakeableMethod
    {
        public void YouCantFakeMe()
        {
            //some implementation
        }
    }

```

代码清单 17:一个带有不可行方法的类

以及该类的以下单元测试:

```cs
    [TestFixture]
    public class WhenTryingToFakeAndUnfakeableMethod
    {
        private AClassWithAnUnfakeableMethod sut;

        [SetUp]
        public void Given()
        {
            sut = A.Fake<AClassWithAnUnfakeableMethod>();
            sut.YouCantFakeMe();
        }

        [Test]
        public void YouWillGetAnException()
        {
            A.CallTo(() => sut.YouCantFakeMe()).MustHaveHappened();
        }
    }

```

代码清单 18:不可行方法的单元测试

| ![](img/note.png) | 注意:不用担心。一定发生了什么事，或者打了个电话。我们将在第 7 章“断言”中讨论这两个项目 |

当我们运行单元测试时，它失败了:

![](img/image019.png)

图 16:当试图伪造一个不可重写的成员时单元测试失败

请注意，FakeItEasy 正在执行这些现成的规则，并具体告诉我们为什么在运行单元测试时会收到异常:

"当前代理生成器无法拦截指定的方法，原因如下:
**-无法拦截非虚拟方法。**”

### 可重写成员可以伪造

就拿我们上面的课来说，把`YouCantFakeMe`方法改成`virtual`，改名为`YouCanFakeMe`。这是我们的班级:

```cs
    public class AClassWithAFakeableMethod
    {
        public virtual void YouCanFakeMe()
        {
            //some implementation
        }
    }

```

代码清单 19:您可以调用现在是一个可伪造的方法，因为它被声明为虚拟的

以及更新后的单元测试:

```cs
    [TestFixture]
    public class WhenTryingToFakeAFakeableMethod
    {
        private AClassWithAFakeableMethod sut;

        [SetUp]
        public void Given()
        {
            sut = A.Fake<AClassWithAFakeableMethod>();
            sut.YouCanFakeMe();
        }

        [Test]
        public void YouCanFakeThisMethod()
        {
            A.CallTo(() => sut.YouCanFakeMe()).MustHaveHappened();
        }
    }

```

代码清单 AClassWithAFakeableMethod 的单元测试

当我们运行单元测试时，这是我们的输出:

![](img/image020.png)

图 17:当方法声明为虚拟时，单元测试通过

| ![](img/note.png) | 注意:虽然我们已经通过将 YouCanFakeThisMethod 声明为 virtual 来允许我们的单元测试通过，但是将类上的方法更改为 virtual 来允许它们被单元测试并不是一个好的做法。如果希望通过继承类来重写方法，则应将方法声明为虚拟的。如果你发现自己为了单元测试而在类中声明了大量的虚拟方法，这是一个信号，你应该退后一步看看你的设计。您是否可以或者应该使用一个接口来表示您当前使用的类的契约，并在接口实现中实现您的类中曾经存在的功能？像往常一样，没有硬性的规则，但是要注意这种类型的代码会悄悄进入你的代码库。 |

## 总结

在这一章中，我们已经讨论了管理 FakeItEasy 如何工作的更高层次的“规则”，并查看了说明如何使用它以及如何不使用它的示例代码。接下来，我们将通过学习如何创建赝品来开始学习使用 FakeItEasy。