单元测试c#模拟接口,由另一个接口组成

单元测试c#模拟接口,由另一个接口组成,c#,.net,unit-testing,xunit.net,C#,.net,Unit Testing,Xunit.net,我是个编码新手 我有一门课,a: public class A { public InterfaceB _b; public A() { _b = new B(); } public string functionA() { if(String.IsNullOrEmpty(_b.GetId())) return String.Empty; else i

我是个编码新手

我有一门课,
a

public class A 
{
    public InterfaceB _b;   

    public A() 
    {  
        _b = new B(); 
    } 

    public string functionA() 
    {
        if(String.IsNullOrEmpty(_b.GetId())) 
            return String.Empty;
        else if(String.IsNullOrEmpty(_b.GetKey())) 
            return String.Empty;
        else  
            return _b.GetToken(); 
    } 
} 

public interface InterfaceB 
{    
    string GetId();   
    string GetKey(); 
    string GetToken();
}
我想测试
function
,在这里我可以深入了解
interfaceB
的所有三种方法。在我的单元测试中,我创建了类
A
的一个实例,当我调用它时,我无法设置类
B
的行为

它不断地冲击db,但是我需要它来处理其他测试用例


如何完全模拟它,以便测试整个逻辑?

要能够测试
A
和模拟接口
InterfaceB
,您必须编写
A
,这样它就不必负责创建
InterfaceB
的实例。相反,它通过构造函数接收
InterfaceB
的实例

您将一次又一次地看到这种模式:

public A() 
{  
    private readonly InterfaceB _b;

    public A(InterfaceB b)
    {
        _b = b;
    }

    public string functionA() 
    {
        if(String.IsNullOrEmpty(_b.GetId())) return String.Empty;
        else if(String.IsNullOrEmpty(_b.GetKey())) return String.Empty;
        else  return _b.GetToken(); 
     } 
} 
这称为依赖注入。这意味着类的依赖关系被“注入”到其中,而不是创建它的类。当我们像这样注入构造函数时,我们也称之为“构造函数注入”,但通常它只是“依赖项注入”。能够像您所问的那样模拟接口是我们使用它的原因之一

一些关键细节:

  • 因为
    InterfaceB
    被传递给构造函数,所以
    A
    中没有任何内容“知道”实际实现是什么。它可能是任何东西。因此,
    a
    从未与任何具体实现绑定。(这就是为什么您可以“模拟”
    InterfaceB
  • 字段
    \u b
    只读
    。严格来说,这不是必需的,但这意味着只能从构造函数中设置
    \u b
    ,并且不能再次更改。这强调了
    A
    只接收并使用它。此类从不控制
    \u b
    是什么。创建
    A
    的内容决定了该值是什么
现在,当您编写单元测试时,您可以创建
InterfaceB
的模拟实现,这些实现完全符合您的需要,如

public class MockedInterfaceB : InterfaceB
{
    private string _id;
    private string _key;
    private string _token;

    public MockedInterfaceB(string id, string key, string token);
    {
       _id = id;
       _key = key;
       _token = token;
    }
    public string GetId() {return _id};   
    public string GetKey() {return _key}; 
    public string GetToken() {return _token};
}
然后在单元测试中,您可以使用该实现:

var testA = new A(new MockedInterfaceB("myid","mykey","mytoken"));
您还可以使用类似的工具更轻松地创建这些模拟


当您听说依赖项注入时,通常是在依赖项注入容器(如Castle Windsor、Autofac或Unity)的上下文中。这些都是帮助您实现依赖注入的有用工具。他们值得学习。但是依赖项注入实际上只是关于如何编写类,就像上面的示例中我们“注入”依赖项(
InterfaceB
)在类
A
中,为了能够测试
A
和模拟接口
InterfaceB
,您必须编写
A
,以便它不负责创建
InterfaceB
的实例。相反,它通过构造函数接收
InterfaceB
的实例

您将一次又一次地看到这种模式:

public A() 
{  
    private readonly InterfaceB _b;

    public A(InterfaceB b)
    {
        _b = b;
    }

    public string functionA() 
    {
        if(String.IsNullOrEmpty(_b.GetId())) return String.Empty;
        else if(String.IsNullOrEmpty(_b.GetKey())) return String.Empty;
        else  return _b.GetToken(); 
     } 
} 
这称为依赖注入。这意味着类的依赖关系被“注入”到其中,而不是创建它的类。当我们像这样注入构造函数时,我们也称之为“构造函数注入”,但通常它只是“依赖项注入”。能够像您所问的那样模拟接口是我们使用它的原因之一

一些关键细节:

  • 因为
    InterfaceB
    被传递给构造函数,所以
    A
    中没有任何内容“知道”实际实现是什么。它可能是任何东西。因此,
    a
    从未与任何具体实现绑定。(这就是为什么您可以“模拟”
    InterfaceB
  • 字段
    \u b
    只读
    。严格来说,这不是必需的,但这意味着只能从构造函数中设置
    \u b
    ,并且不能再次更改。这强调了
    A
    只接收并使用它。此类从不控制
    \u b
    是什么。创建
    A
    的内容决定了该值是什么
现在,当您编写单元测试时,您可以创建
InterfaceB
的模拟实现,这些实现完全符合您的需要,如

public class MockedInterfaceB : InterfaceB
{
    private string _id;
    private string _key;
    private string _token;

    public MockedInterfaceB(string id, string key, string token);
    {
       _id = id;
       _key = key;
       _token = token;
    }
    public string GetId() {return _id};   
    public string GetKey() {return _key}; 
    public string GetToken() {return _token};
}
然后在单元测试中,您可以使用该实现:

var testA = new A(new MockedInterfaceB("myid","mykey","mytoken"));
您还可以使用类似的工具更轻松地创建这些模拟

当您听说依赖项注入时,通常是在依赖项注入容器(如Castle Windsor、Autofac或Unity)的上下文中。这些都是帮助您实现依赖注入的有用工具。他们值得学习。但是依赖项注入实际上只是关于如何编写类,就像上面的示例中,我们将依赖项(
InterfaceB
)注入到类
A

,实际上,通过使用它,可以在不更改源代码的情况下测试您的方法。 在创建a的实例之前,您可以将B模拟为未来的实例,然后只需修改B的方法行为即可

例如:

[TestMethod,Isolated]
public void AllBMethodReturnStrings_WillReturnSuccess()
{
    // Arrange
    // Mocking future B's instance
    var fakeIB = Isolate.Fake.NextInstance<B>();
    var realA = new A();

    Isolate.WhenCalled(()=> fakeIB.GetId()).WillReturn("fakeID");
    Isolate.WhenCalled(() => fakeIB.GetKey()).WillReturn("fakeKey");
    Isolate.WhenCalled(() => fakeIB.GetToken()).WillReturn("success");

    // Act
    var result = realA.functionA();

    // Assert
    Assert.AreEqual("success", result);
}
[测试方法,隔离]
public void allb方法返回字符串\u WillReturnSuccess()
{
//安排
//模拟未来B的实例
var fakeIB=Isolate.false.NextInstance();
var realA=新的A();
隔离.WhenCalled(()=>fakeIB.GetId()).WillReturn(“fakeID”);
隔离.WhenCalled(()=>fakeIB.GetKey()).WillReturn(“fakeKey”);
当调用(()=>fakeIB.GetToken()).WillReturn(“成功”);
//表演
var result=realA.functo();
//断言
断言:平等(“成功”,结果);
}
实际上,通过使用,可以在不更改源代码的情况下测试您的方法。 在创建a的实例之前,您可以将B模拟为未来的实例,然后只需修改B的方法行为即可

例如:

[TestMethod,Isolated]
public void AllBMethodReturnStrings_WillReturnSuccess()
{
    // Arrange
    // Mocking future B's instance
    var fakeIB = Isolate.Fake.NextInstance<B>();
    var realA = new A();

    Isolate.WhenCalled(()=> fakeIB.GetId()).WillReturn("fakeID");
    Isolate.WhenCalled(() => fakeIB.GetKey()).WillReturn("fakeKey");
    Isolate.WhenCalled(() => fakeIB.GetToken()).WillReturn("success");

    // Act
    var result = realA.functionA();

    // Assert
    Assert.AreEqual("success", result);
}
[测试方法,隔离]
public void allb方法返回字符串\u WillReturnSuccess()
{
//安排
//模拟未来B的实例
var fakeIB=Isolate.false.NextInstance();
瓦雷拉