Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does NSubstitute .Returns<T>() work?

How does the .Returns<T> (this T value, ... ) extension method work under the hood?

Specifically, how does .Returns know what method it is intended to configure just from the result of executing that method?

Example:

public interface ICalculator { Add(int a, int b); }

// create mock
var calculator = Substitute.For<ICalculator>();

// How does this piece work under the hood?
calculator.Add(1, 2).Returns(3);
like image 916
coder958452 Avatar asked Sep 09 '16 11:09

coder958452


People also ask

How does NSubstitute work?

When we substitute for a class or interface, NSubstitute uses the wonderful Castle DynamicProxy library to generate a new class that inherits from that class or implements that interface. This allows us to use that substitute in place of the original type.

How do we set a return value for a method call on a substitute?

To set a return value for a method call on a substitute, call the method as normal, then follow it with a call to NSubstitute's Returns() extension method. var calculator = Substitute. For<ICalculator>(); calculator.

What is NSubstitute?

NSubstitute is a great library for mocking objects for Test Driven Development (TDD) in . NET.


2 Answers

Whenever a substitute receives a call, it records information about the call, and updates some global state (threadlocal, as Scott pointed out) recording that it was the most recent substitute called.

When .Returns runs, it looks up the last substitute called, then tells the substitute that its last call should be stubbed to return that specific value. (It also removes it from the collection of received calls, so if we run .Received() the stubbed call doesn't get confused for a real one.)

calculator
    .Add(1, 2)   // substitute records Add(1,2) called. Last substitute
                 // set to `calculator`. Returns default `int` in this case.
    .Returns(3)  // Looks up last sub, sets its last call to return 3.

I think this is a reasonable approximation of what happens. To add a little more precision in case you want to look at the code, a substitute is a dynamic proxy which forwards every call to a "call router" which handles all the logic of the substitute (storing calls, configuring calls, adding callbacks etc.). The global state is a SubstitutionContext, which stores the last call router that received a call.

(Repo links are to v4.0.0-rc1 tag. Later versions may change, but the overall idea should remain fairly consistent.)

like image 116
David Tchepak Avatar answered Sep 27 '22 22:09

David Tchepak


I believe that it works by saving a context (called ISubstitutionContext) in thread local storage when a mocked method is called. Then the call to Returns grabs this context and sets appropriate data in the return object.

The actual implementation of the mocked method would (extremely crudely) look something like:

//Dynamically created mock
public int Add(int a, int b)
{
    var context = new SubstitutionContext("Add", ...);

    //CallContext.LogicalSetData or
    //ThreadStatic or
    //ThreadLocal<T> or
    //...

    return 0;
}

//In some extension class
public static ConfiguredCall Returns<T>(this T value, ...)
{
    var context = SubstitutionContext.Current; //Gets from thread local storage
    return context.LastCallShouldReturn(value);
}
like image 39
Scott Perham Avatar answered Sep 27 '22 20:09

Scott Perham