699

I'm trying to figure out if I should start using more of internal access modifier.

I know that if we use internal and set the assembly variable InternalsVisibleTo, we can test functions that we don't want to declare public from the testing project.

This makes me think that I should just always use internal because at least each project (should?) have its own testing project.

Why shouldn't one do this? When should one use private?

1
  • 2
    Worth mentioning - you can often avoid the need for unit testing your internal methods by using System.Diagnostics.Debug.Assert() within the methods themselves.CommentedMar 30, 2017 at 2:32

11 Answers 11

1560

Internal classes need to be tested and there is an assembly attribute:

using System.Runtime.CompilerServices; [assembly:InternalsVisibleTo("MyTests")] 

Add this to the project info file, e.g. Properties\AssemblyInfo.cs, for the project under test. In this case "MyTests" is the test project.

16
  • 99
    This should really be the accepted answer. I don't know about you guys, but when the tests are "too far" from the code they're testing I tend to get nervous. I'm all for avoiding to test anything marked as private, but too many private things might very well point to an internal class that is struggling to be extracted. TDD or no TDD, I prefer having more tests that test a lot of code, than to have few test that exercise the same amount of code. And avoiding to test internal stuff doesn't exactly help to achieve a good ratio.
    – s.m.
    CommentedMay 28, 2012 at 7:50
  • 8
    There's a great discussion going on between @DerickBailey and Dan Tao regarding the semantic difference between internal and private and the need to test internal components. Well worth the read.CommentedJan 21, 2014 at 5:22
  • 49
    Wrapping in and #if DEBUG, #endif block will enable this option only in debug builds.CommentedFeb 4, 2014 at 16:33
  • 32
    This is the correct answer. Any answer that says that only public methods should be unit tested is missing the point of unit tests and making an excuse. Functional testing is black box oriented. Unit tests are white box oriented. They should testing "units" of functionality, not just public APIs.
    – Gunnar
    CommentedAug 18, 2015 at 23:00
  • 20
    For .NET Core - add it to any .cs file in the app. See details here - stackoverflow.com/a/42235577/968003CommentedNov 2, 2017 at 1:13
218

Adding to Eric's answer, you can also configure this in the csproj file:

<ItemGroup> <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> <_Parameter1>MyTests</_Parameter1> </AssemblyAttribute> </ItemGroup> 

Or if you have one test project per project to be tested, you could do something like this in your Directory.Build.props file:

<ItemGroup> <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> <_Parameter1>$(MSBuildProjectName).Test</_Parameter1> </AssemblyAttribute> </ItemGroup> 

See: https://stackoverflow.com/a/49978185/1678053
Example: https://github.com/gldraphael/evlog/blob/master/Directory.Build.props#L5-L12

5
  • 22
    This should be the top answer imo. All the other answers are very outdated as .net is moving away from assembly info and moving the functionality into csproj definitions.CommentedMay 17, 2020 at 15:54
  • This approach won't work in a Unity project, as the csproj files are rebuilt by Unity. In this case the accepted answer is the way to go.
    – piersb
    CommentedJan 10, 2022 at 11:43
  • 2
    Very good answer tbh! It's way cleaner than having to add this AssemblyInfo file.
    – Rickless
    CommentedFeb 6, 2022 at 12:50
  • 11
    Nice, but already outdated. Use <InternalsVisibleTo> instead, as described in ihebiheb's answer.
    – l33t
    CommentedFeb 23, 2023 at 13:34
  • This did not work for me with .NET Framework 4.8. But the @EricSchaefer answer using AssemblyInfo.cs did work.
    – JohnB
    CommentedJun 22, 2023 at 17:32
137

If you want to test private methods, have a look at PrivateObject and PrivateType in the Microsoft.VisualStudio.TestTools.UnitTesting namespace. They offer easy to use wrappers around the necessary reflection code.

Docs: PrivateType, PrivateObject

For VS2017 & 2019, you can find these by downloading the MSTest.TestFramework nuget

2
136

Starting with .NET 5, you can use also this syntax in the csproj file of the project being tested:

 <ItemGroup> <InternalsVisibleTo Include="MyProject.Tests" /> </ItemGroup> 
2
  • 4
    This solutions works great and it's the one with less code ++1CommentedNov 9, 2023 at 17:53
  • 5
    If you use a convention, replacing "MyProject.Test" with "$(MSBuildProjectName).Test", this becomes an even better answer.
    – Will
    CommentedJun 7, 2024 at 15:17
20

I'm using .NET Core 3.1.101 and the .csproj additions that worked for me were:

<PropertyGroup> <!-- Explicitly generate Assembly Info --> <GenerateAssemblyInfo>true</GenerateAssemblyInfo> </PropertyGroup> <ItemGroup> <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute"> <_Parameter1>MyProject.Tests</_Parameter1> </AssemblyAttribute> </ItemGroup> 
1
  • 2
    The addition of explicitly generating assembly info was what finally made it work for me as well. Thank you for posting this!CommentedMar 11, 2020 at 16:16
11

Keep using private by default. If a member shouldn't be exposed beyond that type, it shouldn't be exposed beyond that type, even to within the same project. This keeps things safer and tidier - when you're using the object, it's clearer which methods you're meant to be able to use.

Having said that, I think it's reasonable to make naturally-private methods internal for test purposes sometimes. I prefer that to using reflection, which is refactoring-unfriendly.

One thing to consider might be a "ForTest" suffix:

internal void DoThisForTest(string name) { DoThis(name); } private void DoThis(string name) { // Real implementation } 

Then when you're using the class within the same project, it's obvious (now and in the future) that you shouldn't really be using this method - it's only there for test purposes. This is a bit hacky, and not something I do myself, but it's at least worth consideration.

9
  • 2
    If the method is internal does this not preclude its use from the testing assembly?CommentedFeb 22, 2010 at 15:47
  • 7
    I occasionally use the ForTest approach but I always find it dead ugly (adding code which provides no actual value in terms of production business logic). Usually I find I had to use the approach because the design is somwhat unfortunate (i.e. having to reset singleton instances between tests)
    – ChrisWue
    CommentedApr 3, 2012 at 18:53
  • 2
    Tempted to downvote this - what is the difference between this hack and simply making the class internal instead of private? Well, at least with compilation conditionals. Then it gets really messy.
    – CAD bloke
    CommentedNov 6, 2013 at 10:18
  • 8
    @CADbloke: Do you mean making the method internal rather than private? The difference is that it's obvious that you really want it to be private. Any code within your production codebase which calls a method with ForTest is obviously wrong, whereas if you just make the method internal it looks like it's fine to use.
    – Jon Skeet
    CommentedNov 6, 2013 at 10:28
  • 2
    @CADbloke: You can exclude individual methods within a release build just as easily in the same file as using partial classes, IMO. And if you do do that, it suggests that you're not running your tests against your release build, which sounds like a bad idea to me.
    – Jon Skeet
    CommentedNov 6, 2013 at 10:54
10

You can use private as well and you can call private methods with reflection. If you're using Visual Studio Team Suite it has some nice functionality that will generate a proxy to call your private methods for you. Here's a code project article that demonstrates how you can do the work yourself to unit test private and protected methods:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx

In terms of which access modifier you should use, my general rule of thumb is start with private and escalate as needed. That way you will expose as little of the internal details of your class as are truly needed and it helps keep the implementation details hidden, as they should be.

    5

    In .NET Core 2.2, add this line to your Program.cs:

    using ... using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("MyAssembly.Unit.Tests")] namespace { ... 
      4

      For .NET core you can add the attribute to the namespace as [assembly: InternalsVisibleTo("MyUnitTestsAssemblyName")]. e.g. Something like

      using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Applications.ExampleApp.Tests")] namespace Applications.ExampleApp internal sealed class ASampleClass : IDisposable { private const string ApiVersionPath = @"api/v1/"; ...... ...... ...... } } 
        0

        Add InternalsVisibleTo.cs file to project's root folder where .csproj file present.

        Content of InternalsVisibleTo.cs should be following

        using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("AssemblyName.WhichNeedAccess.Example.UnitTests")] 
          0

          For these working with signed assemblies, this might be helpful. If the project you are making visible is signed, you need to also sign the targeted project and use the following snippet:

           <ItemGroup> <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> <_Parameter1>[ASSEMBLY_NAME], PublicKey=[PUBLIC_KEY]</_Parameter1> </AssemblyAttribute> </ItemGroup> 

          This exposes you assembly and specifies the public key of the signing key.

          In the targeted project (ASSEMBLY_NAME), add this instead (which is the same you use in your other project):

          <SignAssembly>true</SignAssembly> <AssemblyOriginatorKeyFile>[PATH_TO_snk]</AssemblyOriginatorKeyFile> 

          If you need to find the Public Key please see this SO question: Obtain .NET PublicKeyToken from snk file?

            Start asking to get answers

            Find the answer to your question by asking.

            Ask question

            Explore related questions

            See similar questions with these tags.