Premessa: non c’è nulla di nuovo in questo post. Le tecniche mostrate sono comuni e conosciute quando serve il mock di una classe di terze parti che non è completamente mockabile. Il post potrebbe però salvare 30 minuti a qualcuno che ha bisogno del mock di WindowsIdentity. Da usarsi a proprio rischio e pericolo.

Il problema

Devo testare una classe/metodo che dipende da WindowsIdentity. Per esempio:

public void Bar(WindowsIdentity identity)
{
if (identity.Name == "Foo")
throw new Exception("I don't like Foo");

if (identity.Groups.Any(x => x.Value == "Foos"))
throw new Exception("I don't like Foos");

//...
}

Soluzione…quasi

Potrei crearmi 2 tests così – si noti che uso Moq come mocking framework.

[TestMethod]
[ExpectedException(typeof(Exception))]
public void Bar_Should_Not_Like_Foo_WindowsIdentity()
{
Moq.Mock<WindowsIdentity> mockedIdentity = new Moq.Mock<WindowsIdentity>(WindowsIdentity.GetCurrent().Token);
mockedIdentity.Setup(x => x.Name).Returns("Foo"); // expectation can be set

C c = new C();
c.Bar(mockedIdentity.Object);
}

[TestMethod]
[ExpectedException(typeof(Exception))]
public void Bar_Should_Not_Like_Foos_Group()
{
Moq.Mock<WindowsIdentity> mockedIdentity = new Moq.Mock<WindowsIdentity>(WindowsIdentity.GetCurrent().Token);
mockedIdentity.Setup(x => x.Name).Returns("asdf");
var groups = new IdentityReferenceCollection();
groups.Add(new NTAccount("Foos"));
mockedIdentity.Setup(x => x.Groups).Returns(groups); // expectation cannot be set
C c = new C();
c.Bar(mockedIdentity.Object);
}

Il primo test va bene e Moq riesce a settare la mia expectation sul Name correttamente (perchè Name è virtual su WindowsIdentity).

Il secondo test invece fallisce perchè Moq non riesce a settare l’expectation su Groups che non è virtual. Si noti che il problema non è solo di Moq. Che io sappia solo TypeMock è in grado di fare il mock di tutto (o quasi).

Soluzione 2

Per aggirare tale limite si può:

  1. creare un interfaccia che espone i membri di WindowsIdentity che ci interessa usare
  2. creare una classe wrapper per WindowsIdentity che implementi l’interfaccia di cui sopra
  3. usare l’interfaccia al posto di WindowsIdentity nel metodo Bar

Ecco l’interfaccia:

public interface IWindowsIdentity
{
string AuthenticationType { get; }
IdentityReferenceCollection Groups { get; }
TokenImpersonationLevel ImpersonationLevel { get; }
bool IsAnonymous { get; }
bool IsAuthenticated { get; }
bool IsGuest { get; }
bool IsSystem { get; }
string Name { get; }
SecurityIdentifier Owner { get; }
IntPtr Token { get; }
SecurityIdentifier User { get; }
WindowsImpersonationContext Impersonate();
}

La classe wrapper per WindowsIdentity:

public class WindowsIdentityWrapper : IWindowsIdentity
{
private readonly WindowsIdentity windowsIdentity;

public WindowsIdentityWrapper(WindowsIdentity identity)
{
windowsIdentity = identity;
}

public virtual string AuthenticationType
{
get { return windowsIdentity.AuthenticationType; }
}

public virtual IdentityReferenceCollection Groups
{
get { return windowsIdentity.Groups; }
}

public virtual TokenImpersonationLevel ImpersonationLevel
{
get { return windowsIdentity.ImpersonationLevel; }
}

public virtual bool IsAnonymous
{
get { return windowsIdentity.IsAnonymous; }
}

public virtual bool IsAuthenticated
{
get { return windowsIdentity.IsAuthenticated; }
}

public virtual bool IsGuest
{
get { return windowsIdentity.IsGuest; }
}

public virtual bool IsSystem
{
get { return windowsIdentity.IsSystem; }
}

public virtual string Name
{
get { return windowsIdentity.Name; }
}

public virtual SecurityIdentifier Owner
{
get { return windowsIdentity.Owner; }
}

public virtual IntPtr Token
{
get { return windowsIdentity.Token; }
}

public virtual SecurityIdentifier User
{
get { return windowsIdentity.User; }
}

public virtual WindowsImpersonationContext Impersonate()
{
return windowsIdentity.Impersonate();
}
}

E la signature del metodo Bar:

public void Bar(IWindowsIdentity identity)

A questo punto il secondo test diventerà:

[TestMethod]
[ExpectedException(typeof(Exception))]
public void Bar_Should_Not_Like_Foos_Group()
{
var mockedIWindowsIdentity = new Moq.Mock<IWindowsIdentity>();
mockedIWindowsIdentity.Setup(x => x.Name).Returns("asdf");
var groups = new IdentityReferenceCollection();
groups.Add(new NTAccount("Foos"));
mockedIWindowsIdentity.Setup(x => x.Groups).Returns(groups);
C c = new C();
c.Bar(mockedIWindowsIdentity.Object);
}