How to spy the class under test with AutofacContrib.NSubstitute
Asked Answered
P

2

1

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.

Pease answered 25/6, 2015 at 14:45 Comment(4)
You should really be testing that the behaviour of the ActivateItem in the base class takes place, rather than checking that the method itself is called (which is essentially an implementation detail). Is there a reason you can't do that? Subsitute.ForPartsOf might help, but it's generally a bad idea to mock the class you're testing. The method you want to test will also need to be virtual, so without testing it, I'm sceptical that an explicit call to the base.ActivateItem would actually trigger the substitute anyway. Testing for what the method does is likely to be easier.Anchie
I fully agree with that in general. I was going to reply to your comment that this is a CaliburnMicro view-model, and checking ActivateItem behaviour would be hard, given that we go into the UI View-land. But then I just recalled that there should also be an ActiveItem property ... so now I'm just going to check thatPease
there you go! Now I wonder if I should just close this question or whatPease
You could just post a self answer and accept it once the time has passed... Think it's about 24 hoursAnchie
A
1

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.

Anchie answered 25/6, 2015 at 23:8 Comment(3)
thanks for your investigation, I think it's a useful clarification for everyone coming to this threads. Just 2 bits: I wrote base.Xxxx just for the sake of clarity, in the code I'm just calling ActivateItem and I'm not overriding it (although it is overridable). Also, before switching to NSubstitute I was using Moq with Moq.AutoMock and I was spying successfully with mocker.CreateSelfMock<MainPanelViewModel>() and then Mock.Get(viewModel).Verify(vm => vm.ActivateItem(It.IsAny<SomeSpecificScreenType>()));Pease
@Pease Funny, it was the base.Xxx call that made it interesting. If it's just a normal virtual call then you can use Received on the Partial substitute as you've said in your question and it should work fine. The release notes for NSubstitute might be slightly wrong, but they seem to say that ForPartsOf should be in version 1.7.0... github.com/nsubstitute/NSubstitute/blob/master/CHANGELOG.txtAnchie
But then again, the class under test is instantiated by AutofacContrib.NSubstitute with automocking, and I could not see any of its methods that could return a partial substitute. BTW I was playing with its source code in VS, and if I'm not wrong I think ForPartsOf<> was not available. Anyway ...Pease
P
1

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

Pease answered 25/6, 2015 at 15:43 Comment(0)
A
1

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.

Anchie answered 25/6, 2015 at 23:8 Comment(3)
thanks for your investigation, I think it's a useful clarification for everyone coming to this threads. Just 2 bits: I wrote base.Xxxx just for the sake of clarity, in the code I'm just calling ActivateItem and I'm not overriding it (although it is overridable). Also, before switching to NSubstitute I was using Moq with Moq.AutoMock and I was spying successfully with mocker.CreateSelfMock<MainPanelViewModel>() and then Mock.Get(viewModel).Verify(vm => vm.ActivateItem(It.IsAny<SomeSpecificScreenType>()));Pease
@Pease Funny, it was the base.Xxx call that made it interesting. If it's just a normal virtual call then you can use Received on the Partial substitute as you've said in your question and it should work fine. The release notes for NSubstitute might be slightly wrong, but they seem to say that ForPartsOf should be in version 1.7.0... github.com/nsubstitute/NSubstitute/blob/master/CHANGELOG.txtAnchie
But then again, the class under test is instantiated by AutofacContrib.NSubstitute with automocking, and I could not see any of its methods that could return a partial substitute. BTW I was playing with its source code in VS, and if I'm not wrong I think ForPartsOf<> was not available. Anyway ...Pease

© 2022 - 2024 — McMap. All rights reserved.