readable substitution of IEnumerable of Interfaces

439 views Asked by At

I have the following interfaces

public interface IRibbonCommandsProvider
{
    IEnumerable<IRibbonCommand> GetRibbonCommands();
}
public interface IRibbonCommand
{
    string Group { get; }
    string Tab { get; }
    string Name { get; }
    string Image { get; }
    void Execute();
}

And the follwing substitution code:

public class TabsViewModelTests
{
    [Fact]
    public void Initialize_BuildsCorrectRibbonTree()
    {
        var commands = Substitute.For<IRibbonCommandsProvider>();
        commands.GetRibbonCommands().Returns(
            new[]
            {
                new RibbonCommand { Tab = "Tab1", Group = "Group1", Name = "Name1" },
                new RibbonCommand { Tab = "Tab1", Group = "Group1", Name = "Name2" },
                new RibbonCommand { Tab = "Tab2", Group = "Group1", Name = "Name3" },
                new RibbonCommand { Tab = "Tab2", Group = "Group2", Name = "Name3" }
            });
           ...
    }

    private class RibbonCommand : IRibbonCommand
    {
        public string Group { get; set; }
        public string Tab { get; set; }
        public string Name { get; set; }
        public string Image { get; set; }
        public void Execute() {}
    }
}

Using NSubstitute, is there a clever way to get rid of the stub RibbonCommand class (that is nothing but a fake IRibbonCommand implementation - and that's NSubstitute's job) and still have list of fake ribbon commands that is as easily readable as the above?.

I can't come up with a readable way using NSubsitute's .Returns() fluent method without ending with a lot more (and unreadable) code.

Update: A cool NSubstitute extension method could look like this. I just don't know if and how this can be built:

public static ConfiguredCall ReturnsMany<T>(
    this IEnumerable<T> value,
    Action<T> configureThis,
    params Action<T>[] configureThese)
{
    ...
}

It would be used like this:

commands.GetRibbonCommands().ReturnsMany(
    subst =>
    {
        subst.Tab.Returns("Tab1");
        subst.Group.Returns("Group1");
        subst.Name.Returns("Name1");
    },
    subst =>
    {
        subst.Tab.Returns("Tab1");
        subst.Group.Returns("Group1");
        subst.Name.Returns("Name2");
    },
    subst =>
    {
        subst.Tab.Returns("Tab2");
        subst.Group.Returns("Group1");
        subst.Name.Returns("Name3");
    },
    subst =>
    {
        subst.Tab.Returns("Tab2");
        subst.Group.Returns("Group1");
        subst.Name.Returns("Name3");
    });
4

There are 4 answers

5
David Tchepak On

I think what you've got is very good — quite succinct and clear.

If you really want to get rid of the class you can use a substitute creation method for IRibbonCommand:

    private IRibbonCommand Create(string tab, string group, string name)
    {
        var cmd = Substitute.For<IRibbonCommand>();
        cmd.Tab.Returns(tab);
        cmd.Group.Returns(group);
        cmd.Name.Returns(name);
        return cmd;
    }

    [Fact]
    public void Initialize_BuildsCorrectRibbonTree()
    {
        var ribbonCommands = new[] {
            Create("tab1", "group1", "name1"),
            Create("tab1", "group1", "name2"),
            Create("tab2", "group1", "name3"),
            Create("tab2", "group1", "name4")
        };
        var commands = Substitute.For<IRibbonCommandsProvider>();
        commands.GetRibbonCommands().Returns(ribbonCommands);
        // ...
    }

This doesn't buy you much, although it does mean your test code will be more protected from changes to the IRibbonCommand interface (e.g. an additional property will not require changing your test code), and means you can check received calls and stub other calls on individual items.


Aside: Can use argument names if you want to more closely match the original code:

    Create(tab: "tab1", group: "group1", name: "name1"),
2
forsvarir On

I don't see anything out of the box that's going to do what you're after. One option might be for you to write your own extension method to make the construction easier. So, something like this:

public static class ReadOnlySubstitute {
   static public T For<T>(object source) where T : class {
      var sub = Substitute.For<T>();

      foreach (var prop in source.GetType().GetProperties()) {
         sub.GetType().GetProperty(prop.Name).GetValue(sub).Returns(prop.GetValue(source));
      }
      return sub;
   }
}

The above code essentially creates a substitute for the given interface and then sets up a return on each of properties specified in the supplied object.

This could then be used in your test like this to supply anonymous objects with the parameters:

[Test]
public void Initialize_BuildsCorrectRibbonTree() {
    var ribbonCommands = new[]
    {
       ReadOnlySubstitute.For<IRibbonCommand>(new {Tab="Tab1", Group="Grp1", Name="Nam1"}),
       ReadOnlySubstitute.For<IRibbonCommand>(new {Tab="Tab1", Group="Grp1", Name="Nam2"}),
       ReadOnlySubstitute.For<IRibbonCommand>(new {Tab="Tab2", Group="Grp1", Name="Nam3"}),
       ReadOnlySubstitute.For<IRibbonCommand>(new {Tab="Tab2", Group="Grp2", Name="Nam3"})
    };

    var commands = Substitute.For<IRibbonCommandsProvider>();
    commands.GetRibbonCommands().Returns(ribbonCommands);
    ....
}

It's not quite as concise as using the RibbonCommand class, since you have to construct the array before passing it into the Returns method because NSubstitute gets confused if you try to setup the Returns on the elements at the same time as on the GetRibbonCommands, but I think it's fairly close.

3
dadhi On

As alternative you may setup Command inside test. Then move config func out of the test and optionally generalize for other types as you go. Yagni it.

UPDATED to working test

[Test]
public void Test()
{
    Func<Action<IRibbonCommand>, IRibbonCommand> cmd = config =>
    {
        var c = Substitute.For<IRibbonCommand>();
        config(c);
        return c;
    };

    var ribbonCommands = new[]
    {
        cmd(c => { c.Tab.Returns("Tab1"); c.Group.Returns("Group1"); c.Name.Returns("Name1"); }),
        cmd(c => { c.Tab.Returns("Tab1"); c.Group.Returns("Group1"); c.Name.Returns("Name2"); }),
        cmd(c => { c.Tab.Returns("Tab2"); c.Group.Returns("Group1"); c.Name.Returns("Name3"); }),
        cmd(c => { c.Tab.Returns("Tab2"); c.Group.Returns("Group1"); c.Name.Returns("Name4"); })
    };

    var commandsProvider = Substitute.For<IRibbonCommandsProvider>();
    commandsProvider.GetRibbonCommands().Returns(ribbonCommands);
}
0
forsvarir On

This is really an enhancement (subjective) of @dadhi's answer, combined with an answer from @David Tchepak to a different question.

So, rather than having to create a new Func for each interface your want to use, as described by @dadhi, you can instead create a generic method that takes an Action. You could be this in a shared class, something like this:

static class ConfiguredSub {
    public static T For<T>(Action<T> config) where T : class {
        var c = Substitute.For<T>();
        config(c);
        return c;
    }
}

The problem that I encountered with my other answer was that if you have nested Returns, NSubstitute gets confused and starts throwing exceptions. It turns out that as described by @David here, you can pass a Func to defer the execution and get round this issue. If you combine these two things, then you get something pretty close to what you're after.

[Test]
public void Initialize_BuildsCorrectRibbonTree() {

    var commands = Substitute.For<IRibbonCommandsProvider>();
    commands.GetRibbonCommands().Returns(x => new[] {    
        ConfiguredSub.For<IRibbonCommand>(subst => 
                                      { 
                                          subst.Tab.Returns("Tab1"); 
                                          subst.Group.Returns("Group1"); 
                                          subst.Name.Returns("Name1"); 
                                      }),
        ConfiguredSub.For<IRibbonCommand>(subst => 
                                      { 
                                          subst.Tab.Returns("Tab1"); 
                                          subst.Group.Returns("Group1"); 
                                          subst.Name.Returns("Name2"); 
                                      }),
        ConfiguredSub.For<IRibbonCommand>(subst => 
                                      { 
                                          subst.Tab.Returns("Tab2"); 
                                          subst.Group.Returns("Group1"); 
                                          subst.Name.Returns("Name3"); 
                                      }),
        ConfiguredSub.For<IRibbonCommand>(subst => 
                                      { 
                                          subst.Tab.Returns("Tab2"); 
                                          subst.Group.Returns("Group1"); 
                                          subst.Name.Returns("Name4"); 
                                      })
    });

    // ...

}