How to spy the class under test with AutofacContri

2020-04-30 19:16发布

I'm running unit tests in a class library project with NSpec framework, AutofacContrib.NSubstitute v3.3.2.0, NSubstitute v1.7.0.0 (the latest as of now is 1.8.2).

The Class Under Test instance is built with AutoSubstitute, in order to automock all its needed dependencies.

AutoSubstitute autoSubstitute = new AutoSubstitute();

MainPanelViewModel viewModel = autoSubstitute.Resolve<MainPanelViewModel>();

If working properly, my Class Under Test at some point will invoke one of it's base class methods with some specific input parameter (the base class is out of my control):

// ...
base.ActivateItem(nextScreen);
// ...

So, for test expectation, I need to check (spy) that the instance invokes the base method:

viewModel.Received().ActivateItem(Arg.Any<SomeSpecificScreenType>());

Here's the problem: when I try to do this, at runtime NSubstitute complains that I can only ran Received() against an object created with Substitute.For<>(). I also checked quickly AutofacContrib.NSubstitute source code, but I could not find a way to obtain the instance with automocking and at the same time wrap it somehow in a spy object or something like that.

I also thought that maybe Substitute.ForPartsOf<>() could be helpful, but that method does not seem to be found in NSubstitute v1.7.0.

For sake of completeness, here's NSubstitute full error:

NSubstitute extension methods like .Received() can only be called on objects created using Substitute.For() and related methods.

2条回答
Root(大扎)
2楼-- · 2020-04-30 20:03

So, the actual issue has not been really solved: it's just that the issue itself disappeared.

In order to check for correct behaviour, I recalled that I could also resort to an ActiveItem public property from base class, so with that I stopped using Receive() and went back to simple value comparison.

Still, for future reference, I did not find a way to spy the Class Under Test with these libraries. I know that spying the class under test should be avoided, but as with many things, sometimes you need to do that.

HTH

查看更多
Emotional °昔
3楼-- · 2020-04-30 20:05

For completeness, I did some experimenting with NSubstitute's partial substitutions with ForPartsOf.

The way ForPartsOf works is essentially to define a new class that inherits from the class you're using as a template. This restricts what it is that the mocking framework can intercept to methods that are either defined as abstract or virtual. This is the same limitation you would have for modifying the behaviour of a class if you were to inherit from it with your own class.

Taking this information, lets look at your problem. You want to intercept this call:

base.ActivateItem(nextScreen);

So, because of the limitations above, for you to be able to intercept a call to ActivateItem, the method has to be marked as virtual in the base class. If it's not, there's nothing you can do without changing the application structure.

If the method is marked as virtual, then you can intercept it with NSubstitute but you can only do it if the NSubstituted implementation is called. This works through normal method dispatch, because the highest level implementation of a virtual method is called (the one provided by NSubstitute) when you invoke it. However, it doesn't work when you're calling the method via the base reference.

So, whilst you could intercept this:

ActivateItem(nextScreen)

You simply can't intercept this:

base.ActivateItem(nextScreen);

The fact that you're using base.ActivateItem in your code suggests that your class under test has its own implementation of the method that you don't want to call, so with your current tools you can't achieve what you were trying to do. Which is why it's a good thing that you found a workaround.

You're in the same situation with most other mocking frameworks, including Moq. The exception is TypeMock, which uses a totally different way to intercept method calls which means that it can do things that other frameworks simply can't.

查看更多
登录 后发表回答