23

We are currently in the process of moving from RhinoMocks to NSubstitute.

I have a method that takes an object of type DatabaseParams. This class has the following structure (simplified):

public class DatabaseParams
  {
    public string StoredProcName { get; private set; }
    public SqlParameter[] Parameters { get; private set; }

    public DatabaseParams(string storeProcName, SqlParameter[] spParams)
    {
      StoredProcName = storeProcName;
      Parameters = spParams;
    }
  }

I have the following method I want to check the arguments being passed to it are correct:

public interface IHelper
{
Task<object> ExecuteScalarProcedureAsync(DatabaseParams data);
}

How do I test that an instance of DatabaseParams was passed into that method with the correct values?

I could do this in RhinoMocks with something like this:

helperMock.Expect(m => m.ExecuteScalarProcedureAsync(Arg<DatabaseHelperParameters>.Matches(
        p =>   p.StoredProcName == "up_Do_Something"
            && p.Parameters[0].ParameterName == "Param1"
            && p.Parameters[0].Value.ToString() == "Param1Value"
            && p.Parameters[1].ParameterName == "Param2"
            && p.Parameters[1].Value.ToString() == "Param2Value"
        ))).Return(Task.FromResult<DataSet>(null));

The helperMock is mocking the interface IHelper that contains the ExecuteScalarProcedureAsync method.

JBond
  • 3,062
  • 5
  • 27
  • 31

3 Answers3

27

I've figured out the answer myself.

NSubstitute just needs to use the .Received() call and then when you specify your argument to the method. You can specify the argument matching as a predicate.

For example:

  helperMock.Received().ExecuteScalarProcedureAsync(Arg.Is<DatabaseParams>(
   p =>   p.StoredProcName == "up_Do_Something"
        && p.Parameters[0].ParameterName == "Param1"
        && p.Parameters[0].Value.ToString() == "Param1Value"
        && p.Parameters[1].ParameterName == "Param2"
        && p.Parameters[1].Value.ToString() == "Param2Value"));
JBond
  • 3,062
  • 5
  • 27
  • 31
  • For my use case, I had to make the call on my object with `ReceivedWithAnyArgs` rather than `Received`. – youngrrrr Apr 23 '21 at 21:03
22

There are two approaches that allow you to call assertions against specific properties, which give you better feedback on which properties of the argument object are incorrect.

Do

An alternative is to use Do (see https://nsubstitute.github.io/help/actions-with-arguments/). For example:

StoredProc sp = null; // Guessing the type here

// Setup Do to capture arg
helperMock.ExecuteScalarProcedureAsync(Arg.Do<DatabaseParams>(p => sp = p));

// Call method
helperMock.ExecuteScalarProcedureAsync(dbParams);

// NUnit assertions, but replace with whatever you want.
Assert.AreEqual("up_Do_Something", sp.StoredProcName);
Assert.AreEqual("Param1", p.Parameters[0].ParameterName);
Assert.AreEqual("Param1Value", p.Parameters[0].Value.ToString());
Assert.AreEqual("Param2", p.Parameters[1].ParameterName);
Assert.AreEqual("Param2Value", p.Parameters[1].Value.ToString());

ReceivedCalls

The ReceivedCalls method can also be used, avoiding the need to have to call Arg.Do prior to calling the method under test.

// Call method
helperMock.ExecuteScalarProcedureAsync(dbParams);

var sp = helperMock.ReceivedCalls().ToList().GetArguments()[0];

// NUnit assertions, but replace with whatever you want.
Assert.AreEqual("up_Do_Something", sp.StoredProcName);
Assert.AreEqual("Param1", p.Parameters[0].ParameterName);
Assert.AreEqual("Param1Value", p.Parameters[0].Value.ToString());
Assert.AreEqual("Param2", p.Parameters[1].ParameterName);
Assert.AreEqual("Param2Value", p.Parameters[1].Value.ToString());

The ReceivedCalls method is not listed in the official documentation, but it does work in the above scenario.

Castrohenge
  • 8,525
  • 5
  • 39
  • 66
  • 6
    Very nice solution! For me it did not work when setting it up like this. I had to first set up `helperMock.ExecuteScalarProcedureAsync(Arg.Do(p => sp = p));` (that is, without the `Received()` part). Then, later on, when `ExecuteScalarProcedureAsync()` is called, `sp` will be set. – Tobias Sep 28 '20 at 14:03
  • Thanks for the feedback. Maybe there's been a change in the API. I've been away from the .NET world for a bit, so good to get some clarification. – Castrohenge Sep 28 '20 at 16:37
  • 1
    Take note that you have to Arrange the capturing of the variable BEFORE you Act in your test. When you Assert, it will be there. – Jess Nov 15 '22 at 21:32
2

A bit late for the party, but ran into the same need. I am working with mockito in java, and they have an Argument capture helper that I like. It is basically the same as @Castrohenge answer

So here is my NSubstitute implementation.

public interface IFoo
{
    void DoSomthing(string stringArg);
}

Argument capture class

public class ArgCapture<T>
{
    private List<T> m_arguments = new List<T>();

    public T capture()
    {
        T res = Arg.Is<T>(obj => add(obj)); // or use Arg.Compat.Is<T>(obj => add(obj)); for C#6 and lower
        return res;
    }

    public int Count
    {
        get { return m_arguments.Count; }
    }

    public T this[int index]
    {
        get { return m_arguments[index]; }
    }

    public List<T> Values {
        get { return new List<T>(m_arguments);}
    }

    private bool add(T obj)
    {
        m_arguments.Add(obj);
        return true;
    }
}

And the usage test case

    [Test]
    public void ArgCaptureTest()
    {
        IFoo foo1 = Substitute.For<IFoo>();
        ArgCapture<string> stringArgCapture = new ArgCapture<string>();
        foo1.DoSomthing("firstCall");
        foo1.DoSomthing("secondCall");
        foo1.Received(2).DoSomthing(stringArgCapture.capture());
        Assert.AreEqual(2,stringArgCapture.Count);
        Assert.AreEqual("firstCall",stringArgCapture[0]);
        Assert.AreEqual("secondCall", stringArgCapture[1]);
    }