Re: [nsubstitute] Non-virtual member in a base class prevents from substituting chain

893 views
Skip to first unread message

David Tchepak

unread,
Aug 14, 2012, 12:21:58 AM8/14/12
to nsubs...@googlegroups.com
Hi Vladimir,

Classes with non-virtual members do not get automatically substituted. There is some information on this here:


You can fix this particular case by manually configuring a substitute for the HeadingBlock property:

[Test]
public void Example() {
            var text = Substitute.For<ArticleTextContentBlock>();
            //Manually configure HeadingBlock substitute. This property won't be done automatically as it has non-virtual members.
            text.HeadingBlock.Returns(Substitute.For<HeadingContentBlock>());
            text.HeadingBlock.Heading.Returns("heading");

            Assert.AreEqual(text.HeadingBlock.Heading, "heading");
}

This test passes for me with the classes you provided.

Please let me know if you need more information.

Regards,
David

On Tue, Aug 14, 2012 at 12:45 AM, Vladimir Levchuk <vladimi...@gmail.com> wrote:
Hi,

I have the following problem (cut from the real classes):

    public class BreakingBase
    {
        public void NonVirtual()
        {}
    }

    public class BaseBlock : BreakingBase { }

    public class ArticleTextContentBlock : BaseBlock
    {
        public virtual HeadingContentBlock HeadingBlock { get; set; }
    }

    public class HeadingContentBlock : BaseBlock
    {
        public virtual string Heading { get; set; }
    }
...
[Test]
...
            var text = Substitute.For<ArticleTextContentBlock>();
            text.HeadingBlock.Heading.Returns("heading"); // <<<<<<<<< NullReferenceException here
...

Attempt to substitute text.HeadingBlock.Heading chain throws NRE (substituting HeadingBlock first and then it's Heading value in 2 lines still works fine). Removing BreakingBase.NonVirtual method from the base class solves the problem, but in real solution the base class in a 3rd-party library. Are there any way to still use chain substitution?

---
WBR,
Vladimir Levchuk

--
You received this message because you are subscribed to the Google Groups "NSubstitute" group.
To view this discussion on the web visit https://groups.google.com/d/msg/nsubstitute/-/aX1TDh_hBj0J.
To post to this group, send email to nsubs...@googlegroups.com.
To unsubscribe from this group, send email to nsubstitute...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nsubstitute?hl=en.


Vladimir Levchuk

unread,
Aug 14, 2012, 6:31:34 AM8/14/12
to nsubs...@googlegroups.com
Hi David, 

Thank you for your reply. 
Maybe I should call this "Feature request: I'd like to have a way to enable auto-mocking even for classes with non-virtual members". 
I'm working with CMS, and aggregation is the only option to reuse properties there, so the real code looks a bit messy without auto-mocking (all aggregated blocks must be derived from a base class with non-virtual member in a CMS assembly): 
...
            var articlePage = Substitute.For<ArticlePage>();
            articlePage.PageName.Returns("ArticlePage");


            articlePage.ArticleContentBlock.Returns(Substitute.For<ArticleContentBlock>());
            articlePage.ArticleContentBlock.ArticleTextBlock.Returns(Substitute.For<ArticleTextContentBlock>());
            articlePage.ArticleContentBlock.ArticleTextBlock.HeadingBlock.Returns((HeadingContentBlock) headingBlock);

            articlePage.ArticleContentBlock.ArticleTextBlock.HeadingBlock.Heading.Returns("heading");
...

David Tchepak

unread,
Aug 14, 2012, 8:04:50 AM8/14/12
to nsubs...@googlegroups.com
I've thought about being able to pass this in as a configuration option to substitutes, Substitute.For<T>(Options) or similar where callers can decide the default behaviour they want. I haven't got a solid idea of what this would look like yet; I haven't come across enough cases that required it to be confident of producing a good solution.

The easiest option may be for you to write your own helper method that stubs this stuff out automatically. Here's some sample code of the kind of thing you'd be looking at:


That would give you full control over what you configure for types from your CMS assembly. You could call this recursively for nested properties.

Would something like that work for you?


To view this discussion on the web visit https://groups.google.com/d/msg/nsubstitute/-/mpjbM5alB3EJ.

Vladimir Levchuk

unread,
Aug 15, 2012, 5:20:04 AM8/15/12
to nsubs...@googlegroups.com
Hi David, 

Thank you very much. You're building a cool library! For now I end up with the following (I called my helper Mock since Substitute is not partial): 

            var articlePage = Mock.Recursive.IgnoreType<XhtmlString>().Substitute<ArticlePage>();
            articlePage.PageName.Returns("ArticlePage");

The helper is:

    public static class Mock
    {
        public static RecursiveMockStrategy Recursive
        {
            get { return With<RecursiveMockStrategy>(); }
        }

        public static TStrategy With<TStrategy>()
            where TStrategy: IMockStrategy
            , new() // TODO: ?replace with DI
        {
            return new TStrategy();
        }
    }

If it's interesting I'll share the rest of code too, but the main idea - each strategy supports proper fluent to configure substitutes. My API supports:

public partial class RecursiveMockStrategy
{
...
    public RecursiveMockStrategy IgnoreType<T>() {...}
    public ConditionClause For(Func<PropertyInfo, bool> condition) {...}
    public ConditionClause For(Func<object, PropertyInfo, bool> condition) {...}
    
    public class ConditionClause
    {
         ...
         public RecursiveMockStrategy Ignore() {...}
         public RecursiveMockStrategy MockUsing(Action<object, PropertyInfo> action) {...}

David Tchepak

unread,
Aug 17, 2012, 2:32:18 AM8/17/12
to nsubs...@googlegroups.com
Nice work! Glad you got it sorted. :)
I'm interested in seeing the code, but being honest with myself I'm not going to have much of a chance to look into incorporating something like this anytime soon, so no rush.
If it is something you think is reusable for others, maybe push it up to github or similar as a contrib/complementary project?

To view this discussion on the web visit https://groups.google.com/d/msg/nsubstitute/-/u3uiHrCKoX0J.
Reply all
Reply to author
Forward
0 new messages