2

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");
    });
bitbonk
  • 48,890
  • 37
  • 186
  • 278

4 Answers4

2

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"),
David Tchepak
  • 9,826
  • 2
  • 56
  • 68
  • I thought about this approach, however creating a method for every interface, rather than creating a class seemed like it was just trading one extra piece of work for another, which is why I went down the generic approach with my answer. – forsvarir Jun 11 '15 at 07:17
  • As it stands, your code won't work (at least it didn't for me). You can't setup the `Returns` on the `IRibbonCommand` substitutes from within the `Returns` for the `GetRibbonCommands` method. You have to create the array first, then pass it in, which is why I'm using the `ribbonCommands` variable in my answer. Otherwise, you get an exception from NSubstitute when you run the test. – forsvarir Jun 11 '15 at 07:20
  • @forsvarir: I agree this doesn't gain/lose us much in terms of code size/work required. The main benefits are making the code more tolerant of changes to the interface, and letting us query received calls and stub other members for each item. – David Tchepak Jun 11 '15 at 07:36
  • @forsvarir: oh very good point about the bug in my code! I have updated the answer to fix it. Thanks! :) – David Tchepak Jun 11 '15 at 07:38
  • I think the "extra variable" problem can be avoided by using a lambda to defer creation of the return value. See https://github.com/nsubstitute/NSubstitute/issues/184#issuecomment-111053567 – David Tchepak Jun 11 '15 at 09:13
2

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);
}
dadhi
  • 4,807
  • 19
  • 25
  • 1
    Your call to `config(c)` is missing a semi-colon at the end of the line. If this is fixed, then as it stands the code won't work, it will result in a `CouldNotSetReturnDueToNoLastCallException` from the outer `Returns` call. Perhaps you're missing something else. – forsvarir Jun 12 '15 at 13:19
  • @forsvarir Thanks for finding error in code. My apologies, was written from mobile without chance to check. Updated to working version. Quite succinct. – dadhi Jun 18 '15 at 06:52
  • I've switched my downvote now that your code is working – forsvarir Jun 18 '15 at 07:34
1

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.

forsvarir
  • 10,749
  • 6
  • 46
  • 77
  • The other downside is that I do not have intellisense for the `source` parameter of the `For` method. – bitbonk Jun 10 '15 at 21:49
  • @bitbonk Indeed... I glossed over that since you were concerned with *readability*. I think only having the getters on the properties in the interface ties your hands a bit as far as intellisense goes. Personally, I'd probably stick with the `RibbonCommand` class because the overhead isn't *that* great and it'll probably keep your life simpler in the long run. Of course, in time, somebody might come up with a better answer that proves me wrong :) – forsvarir Jun 10 '15 at 22:00
1

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"); 
                                      })
    });

    // ...

}
Community
  • 1
  • 1
forsvarir
  • 10,749
  • 6
  • 46
  • 77