Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revised chapter 'Internal members' #706

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions docs/help/_posts/2015-01-01-how-nsub-works.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,34 @@ If `DoStuffWith(string s)` is not `virtual`, the `SubstituteForOriginal` class w

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-analysers) to detect these cases at compile time.

### Internal members
### Internal members and types

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:
Similar limitations apply to `internal` members and types. Because `SubstituteForOriginal` gets generated in a separate assembly, they are invisible to NSubstitute and your test-assembly 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.
The first possibility is to change the visibility of the member or type that shall be substituted. If you change the visibility of members to [`protected internal`](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/protected-internal), they get _visible_ for types that derive from it. The visibility of `internal` types must be set to [`public`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/public). However, keep in mind that changing visibilities should be well considered. The next alternative has less side effects on the maintainability of your project.

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 second possibility is to make the `internal` members and types visible to your test-assembly and to the library that NSubstitute uses under the hood. For this you need to _allow_ specific assemblies to accesss the `internal`s. You can do this by either adding an attribute to the assembly that contains the `internal`s or add an annotation to your project file.

#### Option 1. Use an assembly attribute
Add the following code to an arbitrary `.cs`-file of the project that contains the `internal` types.
```
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("name of the assembly that contains your tests")]
```

#### Option 2. Use a tag in the project file (works with .NET 5 and above)
Add an `ItemGroup` that contains the `InternalsVisibleTo`-element beneath the `Project`-element in your `.csproj`-file.
```xml
<Project Sdk="Microsoft.NET.Sdk">
...
<ItemGroup>
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
<InternalsVisibleTo Include="name of the assembly that contains your tests" />
</ItemGroup>
</Project>
```

Remember that if members are 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-analysers) will also detect attempts to use `internal` members at compile time, and will suggest fixes for these cases.

Expand Down