如何模拟和设置局部变量
我正在使用模拟和 autoFixture 库进行 .NET CORE 3.1 nUnit 测试。我需要模拟遥测变量,不知道如何实现它?
var telemetry = telemetryInit as TelemetryInitializerStandard;
我试图模拟这个变量,否则它将为空并且我无法分配该值。遥测需要是 TelemetryInitializerStandard 实例。
公开课ABC { 私有ILogger日志; 私有只读ITelemetryInitializer telemetryInit;
public ABC(ILogger logger, ITelemetryInitializer telemetryInit)
{
log = logger;
this.telemetryInit = telemetryInit;
}
public async Task<List<Customer>> RunAsync()
{
var telemetry = telemetryInit as TelemetryInitializerStandard;
// remaining code
}
}
测试
[TestFixture]
public class AbcTest
{
private readonly ABC _sut;
private readonly Mock<ILogger> _loggerMoq;
private readonly Mock<ITelemetryInitializer> _telemetryInitializerMoq;
public AbcTest()
{
this._loggerMoq = new Mock<ILogger>();
this._telemetryInitializerMoq = new Mock<ITelemetryInitializer>();
this._sut = new DiscoverFA(_loggerMoq.Object, _telemetryInitializerMoq.Object);
[Test]
public void Test1()
{
//Arrange
var fixture = new Fixture();
var telemetryMoq = fixture.Create<TelemetryInitializerStandard>();
}
I am working on .NET CORE 3.1 nUnit tests with mock and autoFixture library. I need to mock telemetry variable not sure how to acheive it?
var telemetry = telemetryInit as TelemetryInitializerStandard;
I am trying to mock this variable otherwise it will be null and I cannot assign the value. The telemetry require to be TelemetryInitializerStandard instance.
public class ABC
{
private ILogger log;
private readonly ITelemetryInitializer telemetryInit;
public ABC(ILogger logger, ITelemetryInitializer telemetryInit)
{
log = logger;
this.telemetryInit = telemetryInit;
}
public async Task<List<Customer>> RunAsync()
{
var telemetry = telemetryInit as TelemetryInitializerStandard;
// remaining code
}
}
Test
[TestFixture]
public class AbcTest
{
private readonly ABC _sut;
private readonly Mock<ILogger> _loggerMoq;
private readonly Mock<ITelemetryInitializer> _telemetryInitializerMoq;
public AbcTest()
{
this._loggerMoq = new Mock<ILogger>();
this._telemetryInitializerMoq = new Mock<ITelemetryInitializer>();
this._sut = new DiscoverFA(_loggerMoq.Object, _telemetryInitializerMoq.Object);
[Test]
public void Test1()
{
//Arrange
var fixture = new Fixture();
var telemetryMoq = fixture.Create<TelemetryInitializerStandard>();
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
无论我们将
ITelemetryInitializer
作为参数传递给RunAsync()
,还是通过构造函数将其传递到类中并在方法中使用它,问题都是一样的。通过声明参数的类型为 ITelemetryInitializer,我们告诉任何使用该代码的人(包括我们自己),任何实现该接口的东西都可以在这里使用。然后,我们将传入的实例转换为具体类
TelemetryInitializerStandard
并使用具体类上但未包含在接口中的某些属性(TenantId?)。这打破了“ITelemetryInitializer”是一个足够参数的“约定”。在完美的世界中,解决方案是扩展
ITelemetryInitializer
接口以包含我们需要的TelemetryInitializerStandard
中的属性,或者替换ITelemetryInitializer
参数与TelemetryInitializerStandard
之一。 (问题不在于我们使用TelemetryInitializerStandard
,而是ITelemetryInitializer
和TelemetryInitializerStandard
之间不匹配)。很多时候,这不是一个完美的世界,我们无法完全控制我们使用的代码(例如,其他人拥有该接口,我们无法更改它)
可以模拟一个具体的类(至少使用 Moq 4.17.2)但我们只能模拟虚拟的属性/方法
使用上面的代码我得到
A
福
!!抽象泄漏!!
B
Foo(泄漏)
酒吧(泄漏)
C
Foo(嘲笑)
Bar (Mocked)
最重要的是,如果您幸运并且您在
TelemetryInitializerStandard
上使用的属性是虚拟的,那么您可以 Mock 它们,但是,如果您可以控制代码并有时间这样做,我将扩展ITelemetryInitializer
以包含所需的属性。可以传入 TelemetryInitializerStandard,但这意味着对于单元测试,您需要将该类上的所有内容都设置为虚拟,这看起来就像是尾巴在摇狗。
将接口转换为特定类以利用该类上的功能是一种非常强烈/糟糕的代码味道,应该避免。如果我们无法控制 ITelemetryInitializer 接口,也许我们可以将其子类化
并将其传递给 ABC 。
Whether we pass the
ITelemetryInitializer
in as a parameter toRunAsync()
or pass it into the class via the constructor and use it in the method, the problem is the same. We have, by stating that the parameter is of typeITelemetryInitializer
, told anyone using the code (including ourselves) that anything which implements that interface can be used here.We then cast the passed in instance to a concrete class
TelemetryInitializerStandard
and use some property (TenantId?) that is on the concrete class but not included in the interface. This breaks the 'contract' thatITelemetryInitializer
is a sufficient parameter.In a perfect world, the solution would be to extend the
ITelemetryInitializer
interface to include the properties fromTelemetryInitializerStandard
that we need, or, replace theITelemetryInitializer
parameter with aTelemetryInitializerStandard
one. (The problem is not that we are using theTelemetryInitializerStandard
but the mismatch betweenITelemetryInitializer
andTelemetryInitializerStandard
).Too often it is not a perfect world and we do not have full control over the code we use (e.g. someone else owns the interface and we cannot change it)
It is possible to mock a concrete class (at least with Moq 4.17.2) but we can only mock properties/methods which are virtual
With above code I get
A
Foo
!!Abstraction Leak!!
B
Foo (leaky)
Bar (leaky)
C
Foo (Mocked)
Bar (Mocked)
Bottom line, if you are lucky and the properties you are using on
TelemetryInitializerStandard
are virtual, then you can Mock them, but, if you have control of the code and the time to do it, I would extendITelemetryInitializer
to include the properties needed.It is possible to pass in a
TelemetryInitializerStandard
, but that would mean that for unit testing you would need to have everything on that class be virtual, which seems like the tail wagging the dog.Casting an interface to a specific class to make use of functionality on that class is a pretty strong/bad code smell and should be avoided. If we do not have control of the
ITelemetryInitializer
interface, perhaps we can sub-class itand pass that into
ABC
instead.