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ò:
- creare un interfaccia che espone i membri di WindowsIdentity che ci interessa usare
- creare una classe wrapper per WindowsIdentity che implementi l’interfaccia di cui sopra
- 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);
}