Saturday, 12 January 2013

Of Microsoft Fakes, Internal Classes and Signing Keys


Recently there was a question raised in the community about using Microsoft fakes for internal classes and what should be specified in the InternalVisibleTo attribute in the AssemblyInfo file. There was some confusion so I thought it would be useful to write a detailed post about it.

For the purpose of this post, I created a class library (SimpleLibrary.dll) that contain two internal classes:

  1. TextFileReader: Responsible for reading contents from a given text file.
  2. Client: Uses the TextFileReader class.

We will create a simple unit test where we will use generated shims for the TextFileReader class, so that the test is not dependent upon the existence of a text file in the file system. The code for the two classes is as follows:

TextFileReader

namespace SimpleLibrary
{
using System.IO;

internal class TextFileReader{
internal string ReadFile(string fileName)
{
StreamReader file = null;
string text = string.Empty;
try{
file = new StreamReader(fileName);
text = file.ReadToEnd();
}
finally{
if (file != null){
file.Close();
file.Dispose();
}
}
return text;
}
}}

Client 


namespace SimpleLibrary
{
internal class Client {
private TextFileReader fileReader;

internal Client(TextFileReader fileReader){
this.fileReader = fileReader;
}

internal string TestMethod(string fileName){
return fileReader.ReadFile(fileName);
}
}}


As you can see, the TextReaderFile is an internal class with an internal method "ReadFile" and Client is another internal class that depends upon an instance of the TextReaderFile and uses it's "ReadFile" method.

SimpleLibrary.Test


Now lets create a unit test project to test our library, we will call it SimpleLibrary.Test. In order to test our internal classes in the unit test project, we will need to include the "InternalVisibleTo" attribute in the AssemblyInfo.cs file of the SimpleLibrary project as follows:

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("SimpleLibrary.Test")]

Our unit tests can now see the internal classes and we can write unit tests for these. However, if we want to generate fakes for the internal classes, we will also have to include "InternalVisibleTo" attribute for the fakes assembly as well.

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("SimpleLibrary.Fakes")]

Adding the above attribute will allow the generated SimpleLibrary.Fakes class to view internal classes and methods.

Now, we can generate fakes for the library by right clicking on the reference to SimpleLibrary and clicking on the "Add Fakes Assembly" option.

Our very simple unit test is as follows:


namespace SimpleLibrary.Test
{  
using Microsoft.QualityTools.Testing.Fakes;  
using Microsoft.VisualStudio.TestTools.UnitTesting;  
using SimpleLibrary.Fakes;      

[TestClass]      
public class ClientTest  
{      
[TestMethod]
public void TestReadFile()
{
using (ShimsContext.Create()) {

// Arrange
ShimTextFileReader shim = new ShimTextFileReader();              
shim.ReadFileString = (filename) => { return "The file content."; };              
var target = new Client(shim.Instance);              

// Act              
var actual = target.TestMethod(@"C:\somefile.txt");

// Assert              
Assert.AreEqual("The file content.", actual);          
}      
}  }}


The test creates a shim of the TextFileReader class and uses it while calling the TestMethod in the client class.

Strong Named Assemblies:


Now, lets consider that the assembly SimpleLibrary has to be strongly named, so will have to sign the assembly. We do that by generating a key file "SimpleLibraryKey" and use it to sign the assembly as shown

Now compile the solution and you will get the following errors

"Friend assembly reference "SimpleLibrary.Test" is invalid. Strong-name assemblies must specify a public key in their InternalVisibleTo declarations."
"Friend assembly reference "SimpleLibrary.Fakes" is invalid. Strong-name assemblies must specify a public key in their InternalVisibleTo declarations."

To resolve this, we need to sign our SimpleLibrary.Test assembly and change our InternalVisible attribute in the SimpleLibrary project to include the PublicKey as shown below


[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("SimpleLibrary.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001006bec6379ed95c6180028a6bc797cdb256805f6dcb61cbdf4f3f3493c5c25efb411fa33472565a5b5426161fd16a6a10802ae710f2e6828d175a2346e33f3831520bcf00e8584919cdf34ed11e51bb988f624360aeadf9b4dc764adc8aeab9939ad209b35f17f2d63192288b0e9bbe5ac013c56d7575b9277cde19e1be7d74f8b")]

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("SimpleLibrary.Fakes, PublicKey=00240000048000009400000006020000002400005253413100040000010001006bec6379ed95c6180028a6bc797cdb256805f6dcb61cbdf4f3f3493c5c25efb411fa33472565a5b5426161fd16a6a10802ae710f2e6828d175a2346e33f3831520bcf00e8584919cdf34ed11e51bb988f624360aeadf9b4dc764adc8aeab9939ad209b35f17f2d63192288b0e9bbe5ac013c56d7575b9277cde19e1be7d74f8b")]

Please note that by default the generated SimpleLibrary.Fakes.dll assembly will be signed with the same key as used in the original class. If we want, we can also use a different key to sign our fakes assembly. Recompile the project and we got errors similar to


"'SimpleClient' is inaccessible due to its protection level...."

The key step here is to include the KeyFile attribute in the Compilation element of the SimpleLibrary.Fakes file. The attribute will look like following, where Key file is the name of the key file used for signing the assembly

<Compilation KeyFile="SimpleLibraryKey.snk" />

Please note that the key file for the fakes assembly can be different from the original one. As long, as you are specifying the PublicKey of the key file specified in the Compilation attribute in the InternalVisible attribute, your code will compile and you can fake the internal classes.


Update 30/01/2013:

I have received some feedback and stand corrected on the key is used by Microsoft Fakes. The default key used by Fakes will always be the one shown below

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("SimpleLibrary.Fakes, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e92decb949446f688ab9f6973436c535bf50acd1fd580495aae3f875aa4e4f663ca77908c63b7f0996977cb98fcfdb35e05aa2c842002703cad835473caac5ef14107e3a7fae01120a96558785f48319f66daabc862872b2c53f5ac11fa335c0165e202b4c011334c7bc8f4c4e570cf255190f4e3e2cbc9137ca57cb687947bc")]


My original understanding was that the key used by Micorosft Fakes is the same key that signs the assembly  it is faking. This is obviously not correct and this is mentioned clearly in MSDN http://msdn.microsoft.com/en-us/library/hh708916.aspx.  You can of course use a different key and and in that case the &ltCompilation KeyFile=""> would be required.

2 comments:

Oleg Sych said...

I can see how this can be confusing. The two InternalsVisibleTo attributes need to specify correct keys for their respective assemblies. In your example, the second InternalsVisibleTo attribute was correct, as it specified the key Fakes uses to sign assemblies by default. However, the first InternalsVisibleTo attribute was wrong because it specified the Fakes key instead of the key you used to sign your test project.

Hamid said...

Hi Oleg,

Sorry, I should have mentioned I am using the same key to sign both the SampleLibrary assembly and the SampleLibrary.Test assembly.