Can I get NSubstitute to auto mock my concrete classes?

こ雲淡風輕ζ 提交于 2021-02-16 04:33:51

问题


I have an interface which I am mocking with 'NSubstitute' which contains properties that return concreate classes, that is the return value is not an interface. e.g

public interface ISomething
{
    SomeObj First { get; }
    SomeObj Second { get; }
}

The 'SomeObj' concrete class has a default constructor but 'NSubstitute' always returns 'null' for these properties. The class itself is not under my control so I cannot simply make it derive from an interface.

Can 'NSubstitute' mock these type of properties? Or is there a way to override the behaviour? Otherwise I have to manually initialise the mock before the test and that can be a lot of code (even if its reused through a common method).

Perhaps there is a simpler solution that I have over-looked?


回答1:


Classes will be auto-mocked if they have a default (parameterless) constructor and all its members are virtual (see the note in the intro of Auto and recursive mocks). The aim of this is to reduce the potential for unwanted (destructive?) side-effects if we are using a substitute and suddenly hit a non-virtual, unmocked code path that does bad stuff in an instance we thought was fake.

NSubstitute doesn't have a way override this behaviour. Instead, I'd recommend creating all your substitutes via your own factory method (e.g. a static Sub.For<T>(...) method in your test project) that uses NSubstitute to produce a substitute, then applies all the specific initialisation rules you need, like using reflection to stub out values for each class property.

Hope this helps.

Possibly related links:

  • I advise trying to avoid mocking types we don't own.
  • Stack Overflow: Is it recommended to mock concrete class?
  • Hacky factory method sample that subs properties using reflection.



回答2:


It doesn't count as auto-mocking but you did also ask "Or is there a way to override the behaviour?" and "Perhaps there is a simpler solution that I have over-looked?"

This answer relies on the statements in your question that:

  • SomeObj is a class outside of your control, from which I assume it is either separately tested or else not testable
  • SomeObj has a default constructor

Sure, it requires you to "manually initialise the mock before the test" but since you've not told us what this object is it's not possible to know how much work it would take to implement fully.

public class SomeObj
{
    // Non-virtual to prevent auto-mocking
    public void Dummy() { }
}
public interface ISomething
{
    SomeObj First { get; }
    SomeObj Second { get; }
}
[TestMethod]
public void Test_17182355ms()
{
    ISomething mockedSomething = Substitute.For<ISomething>();

    SomeObj firstObj = mockedSomething.First;
    Assert.IsNull(firstObj);
    mockedSomething.First.Returns(new SomeObj());
    mockedSomething.Second.Returns(new SomeObj());
    firstObj = mockedSomething.First;
    Assert.IsNotNull(firstObj);
}

Another approach, though not without its own drawbacks, would be to extract your own interface for SomeObj, something like this:

public interface ISomeObj
{
    void Dummy();
}
public class MySomeObj : SomeObj, ISomeObj
{
}

and then mock ISomeObj in your test.



来源:https://stackoverflow.com/questions/17182355/can-i-get-nsubstitute-to-auto-mock-my-concrete-classes

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!