Skip to content

Commit

Permalink
Merge pull request #540 from dtchepak/doc-updates
Browse files Browse the repository at this point in the history
Doc updates
  • Loading branch information
dtchepak authored Apr 17, 2019
2 parents d855bb5 + be0f9e8 commit 662e123
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 11 deletions.
3 changes: 1 addition & 2 deletions docs/_layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ <h1 style="display: inline"> NSubstitute </h1> <br />
</div>
<div id="nav">
<a href="/help/getting-started">Get started</a> |
<a href="/help.html">Docs</a> |
<a href="https://groups.google.com/group/nsubstitute">Discussion group</a> |
<a href="/help.html">Docs and getting help</a> |
<a href="https://github.com/nsubstitute/NSubstitute">NSub on GitHub</a>
</div>

Expand Down
6 changes: 5 additions & 1 deletion docs/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ body {
p{
font-size:1.0em;
line-height:1.6;
font-family:"Georgia"
}

ul {
font-size:1.0em;
line-height:1.6;
}

h1{font-size:2.5em}
Expand Down
2 changes: 1 addition & 1 deletion docs/help/_posts/2010-01-01-getting-started.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ We can ask NSubstitute to create a substitute instance for this type. We could a
calculator = Substitute.For<ICalculator>();
```

⚠️ **Note**: NSubstitute will only work properly with interfaces or with `virtual` members of classes. Be careful substituting for classes with non-virtual members. See [Creating a substitute](/help/creating-a-substitute/#substituting_infrequently_and_carefully_for_classes) for more information.
⚠️ **Note**: NSubstitute will only work properly with interfaces or with class members that are overridable from the test assembly (`public virtual`, `protected virtual`, `protected internal virtual`, or `internal virtual` with `InternalsVisibleTo` attribute applied). Be careful substituting for classes with non-virtual members. See [Creating a substitute](/help/creating-a-substitute/#substituting_infrequently_and_carefully_for_classes) and [How NSubstitute works](/help/how-nsub-works) for more information.

Now we can tell our substitute to return a value for a call:

Expand Down
4 changes: 2 additions & 2 deletions docs/help/_posts/2010-01-02-creating-a-substitute.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ This is how you'll normally create substitutes for types. Generally this type wi

⚠️ **Warning:** Substituting for classes can have some nasty side-effects!

For starters, **NSubstitute can only work with *virtual* members of the class**, so any non-virtual code in the class will actually execute! If you try to substitute for a class that formats your hard drive in the constructor or in a non-virtual property setter then you're asking for trouble.
For starters, **NSubstitute can only work with *virtual* members of the class** that are overridable in the test assembly, so any non-virtual code in the class will actually execute! If you try to substitute for a class that formats your hard drive in the constructor or in a non-virtual property setter then you're asking for trouble. (By overridable we mean `public virtual`, `protected virtual`, `protected internal virtual`, or `internal virtual` with `InternalsVisibleTo` attribute applied. See [How NSubstitute works](/help/how-nsub-works) for more information.)

It also means features like `Received()`, `Returns()`, `Arg.Is()`, `Arg.Any()` and `When()..Do()` **will not work with these non-virtual members**. For example: `subClass.Received().NonVirtualCall()` will not actually run an assertion (it will always pass, even if there are no calls to `NonVirtualCall()`), and can even cause confusing problems with later tests. These features will work correctly with virtual members of the class, but we have to be careful to avoid the non-virtual ones.
It also means features like `Received()`, `Returns()`, `Arg.Is()`, `Arg.Any()` and `When()..Do()` **will not work with these non-overridable members**. For example: `subClass.Received().NonVirtualCall()` will not actually run an assertion (it will always pass, even if there are no calls to `NonVirtualCall()`), and can even cause confusing problems with later tests. These features will work correctly with virtual members of the class, but we have to be careful to avoid the non-virtual ones.

For these reasons we strongly recommend using [NSubstitute.Analyzers](/help/nsubstitute-analysers/) to detect these cases, and sticking to substituting for interfaces as much as possible. (Interfaces are always safe to substitute and do not suffer from any of the limitations that class substitutes do.)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ For the interface above we can configure the return value and set the output of
//Arrange
var lookup = Substitute.For<ILookup>();
lookup
.TryLookup("hello", out Arg.Is(""))
.TryLookup("hello", out Arg.Any<string>())
.Returns(x => {
x[1] = "world!";
return true;
});

//Act
var value = "";
var result = lookup.TryLookup("hello", out value);
var result = lookup.TryLookup("hello", out var value);

//Assert
Assert.True(result);
Expand Down
4 changes: 3 additions & 1 deletion docs/help/_posts/2013-04-01-threading.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ title: Threading
layout: post
---

It is fairly standard for production code to call a substitute from multiple threads, but we should avoid having our test code configure or assert on a substitute while it is also be used from other threads in production code. See [Issue #256](https://github.com/nsubstitute/NSubstitute/issues/256) for an example of how doing this can result in problems.
It is fairly standard for production code to call a substitute from multiple threads, but we should avoid having our test code configure or assert on a substitute while it is also be used from other threads in production code.

Although this particular issue has been mitigated by work in [#452](https://github.com/nsubstitute/NSubstitute/pull/462), issue [#256](https://github.com/nsubstitute/NSubstitute/issues/256) shows the types of problems that can occur if we're not careful with threading.

To avoid this sort of problem, make sure your test has finished configuring its substitutes before exercising the production code, then make sure the production code has completed before your test asserts on `Received()` calls.

71 changes: 71 additions & 0 deletions docs/help/_posts/2015-01-01-how-nsub-works.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: How NSubstitute works
layout: post
---

When we substitute for a class or interface, NSubstitute uses the wonderful [Castle DynamicProxy library](https://github.com/castleproject/Core) 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.

You can think of it working a bit like this:

<!--
```requiredcode
public static void handle_call_invocation() {}
public static int configured_value_for_call() => 42;
```
-->

```csharp
public class Original {
public virtual int DoStuffWith(string s) => s.Length;
}

// Now if we do:
// var sub = Substitute.For<Original>();
//
// This is a bit like doing:
public class SubstituteForOriginal : Original {
public override int DoStuffWith(string s) {
// Tell NSubstitute to record the call, run when..do actions etc,
// then return the value configured for this call.
handle_call_invocation();
return configured_value_for_call();
}
}
Original sub = new SubstituteForOriginal();
```

## Calamities with classes

For the case when `Original` is an interface this works perfectly; every member in the interface will be intercepted by NSubstitute's logic for recording calls and returning configured values.

There are some caveats when `Original` is a class though (hence all the [warnings about them in the documentation](/help/creating-a-substitute#substituting-infrequently-and-carefully-for-classes)).

### Non-virtual members

If `DoStuffWith(string s)` is not `virtual`, the `SubstituteForOriginal` class will not be able to override it, so when it is called NSubstitute will not know about it. It is effectively invisible to NSubstitute; it can't record calls to it, it can't configure values using `Returns`, it can't run actions via `When..Do`, it can't verify the call was received. Instead, the real base implementation of the member will run.

This can cause all sorts of problems if we accidentally attempt to configure a non-virtual call, because NSubstitute will get confused about which call you're talking about. Usually this will result in a run-time error, but in the worst case it can affect the outcome of your test, or even the following test in the suite, in non-obvious ways. Thankfully we have [NSubstitute.Analyzers](/help/nsubstitute-analyzers) to detect these cases at compile time.

### Internal members

Similar limitations apply to `internal virtual` members. Because `SubstituteForOriginal` gets generated in a separate assembly, `internal` members are invisible to NSubstitute by default. There are two ways to solve this:

* Use `[assembly: InternalsVisibleTo(InternalsVisible.ToDynamicProxyGenAssembly2)]` in the test assembly so that the `internal` member can be overridden.
* Make the member [`protected internal virtual`](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/protected-internal) so that sub-classes can access the member.

Remember that if the member is non-virtual, NSubstitute will not be able to intercept it regardless of whether it is `internal` or `InternalsVisibleTo` has been added.

The good news is that [NSubstitute.Analyzers](/help/nsubstitute-analyzers) will also detect attempts to use `internal` members at compile time, and will suggest fixes for these cases.

### Real code

The final thing to notice here is that there is the potential for real logic from the `Original` class to execute. We've already seen how this is possible for non-virtual members, but it can also happen if `Original` has code in its constructor. If the constructor calls `FileSystem.DeleteAllMyStuff()`, then constructing `SubstituteForOriginal` will also run this when the base constructor gets called.

### Class conclusion

* Be careful substituting for classes!
* Where possible use interfaces instead.
* Remember NSubstitute works by inheriting from (or implementing) your original type. If you can't override a member by manually writing a sub-class, then NSubstitute won't be able to either!
* Install [NSubstitute.Analyzers](/help/nsubstitute-analyzers) where ever you install NSubstitute. This will help you avoid these (and other) pitfalls.


17 changes: 17 additions & 0 deletions docs/help/_posts/2019-01-01-search.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: Search
layout: post
---

<script>
(function() {
var cx = '005697633880271604295:lw9srlgcpg8';
var gcse = document.createElement('script');
gcse.type = 'text/javascript';
gcse.async = true;
gcse.src = 'https://cse.google.com/cse.js?cx=' + cx;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
})();
</script>
<gcse:search></gcse:search>
7 changes: 6 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ Received 2 non-matching calls (non-matching arguments indicated with '*' charact
<li class="nuget">
<a href="https://nuget.org/List/Packages/NSubstitute">Install via NuGet:</a> <code>Install-Package NSubstitute</code>
</li>
<li class="nuget"><a href="/help/nsubstitute-analysers/">Optional analysers for C# and VB</a></li>
<li class="nuget"><a href="/help/nsubstitute-analysers/">Optional analysers for C#:</a>
<code>Install-Package NSubstitute.<wbr>Analyzers.<wbr>CSharp</code>
</li>
<li class="nuget"><a href="/help/nsubstitute-analysers/">Optional analysers for VB:</a>
<code>Install-Package NSubstitute.<wbr>Analyzers.<wbr>VisualBasic</code>
</li>
<li class="github">
<a href="https://github.com/nsubstitute/nsubstitute">Source</a>
</li>
Expand Down

0 comments on commit 662e123

Please sign in to comment.