[英]Mock class which inherit from abstract class and implements interface
Assume following scenario: I have a PhoneController
class which use Phone
class. 假定以下情形:我有一个使用
Phone
类的PhoneController
类。 Phone
is a class which inherit from abstract class Device
and it implements IPhone
interface. Phone
是一个从抽象类Device
继承的类,它实现了IPhone
接口。 For testing PhoneController
I want to mock Phone
class, but I don't know how it might be done using NSubstitute, because Phone
class inherits abstract class and additionally it implements interface. 为了测试
PhoneController
我想模拟Phone
类,但是我不知道如何使用NSubstitute完成它,因为Phone
类继承了抽象类并且另外实现了接口。
Example code: 示例代码:
public abstract class Device
{
protected string Address { get; set; }
}
public interface IPhone
{
void MakeCall();
}
public class Phone : Device, IPhone
{
public void MakeCall()
{
throw new NotImplementedException();
}
}
public class PhoneController
{
private Phone _phone;
public PhoneController(Phone phone)
{
_phone = phone;
}
}
[TestClass]
public class PhoneControllerTests
{
[TestMethod]
public void TestMethod1()
{
// How mock Phone class?
//var mock = Substitute.For<Device, IPhone>();
//usage of mock
//var controller = new PhoneController(mock);
}
}
Controller uses GetStatus
method from Device
abstract class, so _phone
cannot be changed to IPhone
type 控制器使用
Device
抽象类中的GetStatus
方法,因此_phone
不能更改为IPhone
类型
public abstract class Device
{
protected string Address { get; set; }
public abstract string GetStatus();
}
public interface IPhone
{
void MakeCall();
}
public class Phone : Device, IPhone
{
public void MakeCall()
{
throw new NotImplementedException();
}
public override string GetStatus()
{
throw new NotImplementedException();
}
}
public class PhoneController
{
private Phone _phone;
public PhoneController(Phone phone)
{
_phone = phone;
}
public string GetDeviceStatus()
{
return _phone.GetStatus();
}
public void MakeCall()
{
_phone.MakeCall();
}
}
[TestClass]
public class PhoneControllerTests
{
[TestMethod]
public void TestMethod1()
{
// How mock Phone class?
//var mock = Substitute.For<Device, IPhone>();
//usage of mock
//var controller = new PhoneController(mock);
}
}
There are a couple of ways to do this, and which one you choose depends what you're trying to test. 有两种方法可以执行此操作,您选择哪种方法取决于您要测试的内容。
You can mock the IPhone interface (as you have done in your commented out code. 您可以模拟iPhone接口(如注释掉的代码中所做的那样。
You can subclass the Phone class (either manually, or use NSubstitute's .ForPartsOf<>
). 您可以继承Phone类的子类(手动或使用NSubstitute的
.ForPartsOf<>
)。 See here for a blog post on this. 请参阅此处的博客文章。
I find that if I structure my test using the Arrange/Act/Assert method, it's clearer what I'm trying to test (ideally there should be a single call in your Act section; for example: 我发现如果我使用Arrange / Act / Assert方法构造测试,则可以清楚地知道我要测试的内容(理想情况下,您的Act部分应该只有一个调用;例如:
[TestMethod]
public void TestMethod1()
{
// Arrange
var mock = Substitute.For<IPhone>();
var controller = new PhoneController(mock);
// Act
int result = controller.Method();
// Assert
Assert.Equal(result, 3);
}
EDIT - Based on updated comments 编辑-基于更新的评论
You can't unit test an abstract class, because that class doesn't contain (by definition) any code. 您不能对抽象类进行单元测试,因为该类不包含(根据定义)任何代码。 In your scenario, it looks like what you're trying to do is to test the concrete
Phone
class. 在您的方案中,您似乎要尝试测试具体的
Phone
类。 If that's the case, then simply create an instance of the Phone
class and test it; 如果是这样,则只需创建
Phone
类的实例并对其进行测试; you don't need to involve the controller: 您不需要涉及控制器:
// Arrange
var phone = new Phone();
// Act
string result = phone.GetStatus();
// Assert
Assert.Equal("New", result);
If PhoneController
takes a Phone
, then you will have to use a Phone
or a subclass of Phone
. 如果
PhoneController
需要一个Phone
,那么你将不得不使用Phone
或子类Phone
。 Using Substitute.For<Device, IPhone>()
will generate a type a bit like this: 使用
Substitute.For<Device, IPhone>()
将生成如下类型:
public class Temp : Device, IPhone { ... }
Which is not the same as a Phone
. 这与
Phone
。
So there are a few options. 因此,有一些选择。 Ideally I'd consider @pm_2's suggestion of making
PhoneController
take an IPhone
instead. 理想情况下,我会考虑让@ pm_2建议让
PhoneController
IPhone
。 By making it depend on an interface rather than something concrete we gain some flexibility: PhoneController
now can work with anything that adheres to the IPhone
interface, including a mocked IPhone
instance. 通过使它依赖于接口而不是具体的东西,我们获得了一定的灵活性:
PhoneController
现在可以与所有遵循IPhone
接口的东西(包括PhoneController
IPhone
实例)一起使用。 This also means we can change the behaviour of production code by have other implementations (contrived example, maybe an EncryptedPhone : IPhone
that encrypts the call, without requiring a change to PhoneController
). 这也意味着我们可以通过其他实现来更改生产代码的行为(人为的示例,例如
EncryptedPhone : IPhone
可以加密调用,而无需更改PhoneController
)。
If you do need to couple the specific Phone
class to PhoneController
, then mocking immediately becomes more difficult. 如果确实需要将特定的
Phone
类耦合到PhoneController
,则立即进行PhoneController
将变得更加困难。 After all, you are stating "this only works with this specific class", then trying to get it working with another class (the substituted class). 毕竟,您要说“这仅适用于该特定类”,然后尝试使其与另一类(替代类)一起使用。 For this approach, if you are able to make all the relevant members of the
Phone
class virtual
then you can create a substitute using Substitute.For<Phone>()
. 对于这种方法,如果您能够将
Phone
类的所有相关成员virtual
则可以使用Substitute.For<Phone>()
创建替代对象。 If you need to also need to run some of the original Phone
code, but substitute other parts, then you can using Substitute.ForPartsOf<Phone>()
as @pm_2 suggested . 如果您还需要运行一些原始
Phone
代码,但要替换其他部分,则可以按照@ pm_2的建议使用Substitute.ForPartsOf<Phone>()
。 Remember that NSubstitute (and many other .NET mocking libraries) will not mock non- virtual
members, so keep this in mind when mocking classes. 请记住,NSubstitute(和许多其他.NET模拟库)不会模拟非
virtual
成员,因此在模拟类时请记住这一点。
Finally, it is worth considering not mocking Phone
at all and using the real class instead. 最后,值得考虑的是根本不要嘲笑
Phone
,而是使用real类。 If PhoneController
depends on the details of specific Phone
implementation, then testing it with a fake version is not going to tell you whether it works. 如果
PhoneController
取决于特定Phone
实现的详细信息,则使用伪造版本对其进行测试不会告诉您它是否有效。 If instead it only needs a compatible interface, then it is a good candidate for using a substitute. 如果相反,它仅需要兼容的接口,则它是使用替代品的理想选择。 Mocking libraries automate the creation of an alternative type to use with a class, but they will not automate having a design that accommodates the use of that type.
模拟库会自动创建与类一起使用的替代类型,但不会自动设计出可以使用该类型的设计。 :)
:)
I think my previous answer still applies for the additional scenario. 我认为我先前的答案仍然适用于其他情况。 To recap: my first approach is to attempt to decouple the
PhoneController
from a specific implementation; 回顾一下:我的第一种方法是尝试将
PhoneController
与特定的实现分PhoneController
来。 then I'd consider substituting for Phone
directly (with the disclaimer about non- virtual
methods). 那么我会考虑直接用
Phone
(用关于非virtual
方法的免责声明)代替Phone
。 And I'd always keep in mind not mocking at all (this should probably be the first option). 而且我始终牢记一点都不嘲笑(这应该是第一个选择)。
There are many ways we can achieve the first option. 我们有很多方法可以实现第一个选择。 We could update
PhoneController
to take an IDevice
(extract interface from Device
) and an IPhone
, with the constructor PhoneController(IPhone p, IDevice d)
. 我们可以使用构造函数
PhoneController(IPhone p, IDevice d)
更新PhoneController
以获取IDevice
(从Device
提取接口)和IPhone
。 The real code can then be: 实际的代码可以是:
var phone = new Phone();
var controller = new PhoneController(phone, phone);
While the test code could be: 虽然测试代码可以是:
var phone = Substitute.For<IPhone>();
var device = Substitute.For<IDevice>();
var testController = new PhoneController(phone, device);
// or
var phone = Substitute.For<IPhone, IDevice>();
var testController = new PhoneController(phone, (IDevice) phone);
Alternatively we could create IPhone
, IDevice
and then have: 或者,我们可以创建
IPhone
, IDevice
然后具有:
interface IPhoneDevice : IPhone, IDevice { }
public class Phone : IPhoneDevice { ... }
I've seen interface inheritance like this discouraged before, so you may want to look into that first before taking this option. 我以前不鼓励这样的接口继承,因此您可能需要在使用此选项之前先进行研究。
You can also combine these approaches, by replacing IDevice
in the examples above with your current Device
base class and mocking that (disclaimer: non- virtual
s). 您还可以通过将以上示例中的
IDevice
替换为当前的Device
基类并对其进行virtual
(免责声明:non- virtual
s)来组合这些方法。
I think the main question you need to answer is how to you want to couple PhoneController
to its dependencies? 我认为您需要回答的主要问题是如何将
PhoneController
耦合到其依赖项? Think about this in terms of what concrete dependencies does it need, and what can be expressed in terms of logical interfaces. 从需要什么具体依赖关系以及可以用逻辑接口表达的角度考虑这一点。 The answer to that will determine your options for testing.
答案将决定您的测试选项。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.