如何对依赖于私有字段的方法进行单元测试?

huangapple go评论58阅读模式
英文:

How to unit test methods which depend on a private field?

问题

以下是您要翻译的文本部分:

"I just started writing some tests for a new project and encountered the following "problem":

I mocked the dependent INotifyVariableChangedService and OpcClient to test my OpcService:

        private readonly INotifyVariableChangedService _notifyVariableChangedService;
        private readonly ushort _symbolicsIndex;

        public OpcService(INotifyVariableChangedService notifyVariableChangedService)
        {
            this._notifyVariableChangedService = notifyVariableChangedService;

            this._client = new OpcUaClient("", "");

            _symbolicsIndex = _client.GetSymbolicsNameSpaceId();
        }

Here you can see, that i get the _symbolicsIndex, which i only have to do one time. Consequently i'm persisting it in a field and reusing it whenever i need it again.

This brought up a problem when testing a method like this one, because it depends on the value in this field:

        {
            var variableValue = await this._client.GetValue(nodeIdentifier, variableName, _symbolicsIndex);

            return new KeyValuePair<string, object>(variableName, variableValue.Value);
        }

Of course i could refactor this and get the _symbolicsIndex everytime in my controller, but that does not seem like a clean solution at all.

Is there a way to "mock" this field ? Should i even be doing that at all or should i refactor it and expose a method, which sets the _symbolicsIndex in my service explicitly, instead of setting it in the constructor ?

Any help would be greatly appreciated."

英文:

I just started writing some tests for a new project and encountered the following "problem":

I mocked the dependent INotifyVariableChangedService and OpcClient to test my OpcService:

private readonly OpcUaClient _client;
        private readonly INotifyVariableChangedService _notifyVariableChangedService;
        private readonly ushort _symbolicsIndex;

        public OpcService(INotifyVariableChangedService notifyVariableChangedService)
        {
            this._notifyVariableChangedService = notifyVariableChangedService;

            this._client = new OpcUaClient("", "");

            _symbolicsIndex = _client.GetSymbolicsNameSpaceId();
        }

Here you can see, that i get the _symbolicsIndex, which i only have to do one time. Consequently i'm persisting it in a field and reusing it whenever i need it again.

This brought up a problem when testing a method like this one, because it depends on the value in this field:

public async Task<KeyValuePair<string, object>> GetVariableValue(string nodeIdentifier, string variableName)
        {
            var variableValue = await this._client.GetValue(nodeIdentifier, variableName, _symbolicsIndex);

            return new KeyValuePair<string, object>(variableName, variableValue.Value);
        }

Of course i could refactor this and get the _symbolicsIndex everytime in my controller, but that does not seem like a clean solution at all.

Is there a way to "mock" this field ? Should i even be doing that at all or should i refactor it and expose a method, which sets the _symbolicsIndex in my service explicitly, instead of setting it in the constructor ?

Any help would be greatly appreciated.

答案1

得分: 1

I don't think you can mock a private field without using reflection, but I would suggest that you also have the client injected rather than instantiating it in the constructor.

你不能在不使用反射的情况下模拟私有字段,但我建议您将客户端也注入而不是在构造函数中实例化它。

You said the client is already a mocked object, so you could fake the GetSymbolicsNameSpaceId() method and return the desired mocked value, which is then set to the private field when the client is passed to the constructor.

你说客户端已经是一个模拟对象,因此你可以伪造GetSymbolicsNameSpaceId()方法并返回所需的模拟值,然后在客户端传递给构造函数时将其设置为私有字段。

This could then look something like this (assumed FakeItEasy as the mocking library)

这可能看起来像这样(假设使用FakeItEasy作为模拟库)

private readonly OpcUaClient _client;
private readonly INotifyVariableChangedService _notifyVariableChangedService;
private readonly ushort _symbolicsIndex;

public OpcService(INotifyVariableChangedService notifyVariableChangedService, OpcUaClient client)
{
    this._notifyVariableChangedService = notifyVariableChangedService;
    this._client = client;

    _symbolicsIndex = _client.GetSymbolicsNameSpaceId();
}

Test:

_notifyVariableChangedService = A.Fake<INotifyVariableChangedService>();
_client = A.Fake<OpcUaClient>();

A.CallTo(() => _client.GetSymbolicsNameSpaceId()).Returns(1);

_testee = new OpcService(_notifyVariableChangedService, _client);
英文:

I don't think you can mock a private field without using reflection, but I would suggest that you also have the client injected rather than instantiating it in the constructor.

You said the client is already a mocked object, so you could fake the GetSymbolicsNameSpaceId() method and return the desired mocked value, which is then set to the private field when the client is passed to the constructor.

This could then look something like this (assumed FakeItEasy as the mocking libary)

private readonly OpcUaClient _client;
private readonly INotifyVariableChangedService _notifyVariableChangedService;
private readonly ushort _symbolicsIndex;

public OpcService(INotifyVariableChangedService notifyVariableChangedService, OpcUaClient client)
{
    this._notifyVariableChangedService = notifyVariableChangedService;
    this._client = client;

    _symbolicsIndex = _client.GetSymbolicsNameSpaceId();
}

Test:

_notifyVariableChangedService = A.Fake<INotifyVariableChangedService>();
_client = A.Fake<OpcUaClient>();

A.CallTo(() => _client.GetSymbolicsNameSpaceId()).Returns(1);

_testee = new OpcService(_notifyVariableChangedService, _client);

答案2

得分: 0

已经按照francWhite的回答解决了。

如果有人阅读这个,想知道如何在Moq中实现,以下是示例:

[Fact]
public void Test()
{
    var opcClientMock = new Mock<IOpcUaClient>();
    var notifyVariableChangedServiceMock = new Mock<INotifyVariableChangedService>();

    opcClientMock.Setup(x => x.GetSymbolicsNameSpaceId()).ReturnsAsync((ushort)7);            

    var opcService = new OpcService(notifyVariableChangedServiceMock.Object, opcClientMock.Object);
}
英文:

Got it sorted with francWhite's answer.

If anyone reading this, wonders how it would look with Moq, here you go:

[Fact]
    public void Test()
    {
        var opcClientMock = new Mock&lt;IOpcUaClient&gt;();
        var notifyVariableChangedServiceMock = new Mock&lt;INotifyVariableChangedService&gt;();

        opcClientMock.Setup(x =&gt; x.GetSymbolicsNameSpaceId()).ReturnsAsync((ushort)7);            

        var opcService = new OpcService(notifyVariableChangedServiceMock.Object, opcClientMock.Object);
    }

huangapple
  • 本文由 发表于 2023年4月4日 16:12:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/75926981.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定