views:

105

answers:

1

I recently refactored some code in an Active Directory role provider to remove support for multiple domains. In the process my integration tests broke in ways that I didn't expect. The tests do not reliably succeed unless I put significant delays between the test set up code and the code that invoked the method being tested. If I run the test using the debugger it always succeeds and I can't see any problems with the code. If I run the test using the automated tools one or more tests fail and fail in ways that are unexpected.

How can I reliabily test role provider code that uses the System.Directory.AccountManagement namespace classes and methods?

Note: In keeping with the SO paradigm, I'm providing the solution that I found as a separate answer. I'm open to other solutions, however, if you feel that your solution works better than mine. This question is being contributed because I couldn't find any existing questions on SO that addressed my problem.

Some related questions are:

A: 

I discovered that the problem was that the PrincipalSearchers that I was using in the role provider did not always contact the same domain controller as the code used in set up did. This would result in errors due to propagation delays between domain controllers. To solve this problem I used constructor injection to provide the PrincipalContext used in set up to the role provider. This allows the role provider to always use the same context as the test code. In addition I replaced the SearchRoot on the PrincipalSearcher with a search root based on the PrincipalContext provided via constructor injection. Relevant code below. Note that the role provider implements IDisposable in order to dispose of the domain context if one isn't supplied externally.

private bool DisposeContext { get; set; }
private PrincipalContext DomainContext { get; set; }

public PrintAccountingRoleProvider() : this( null ) { }

public PrintAccountingRoleProvider( PrincipalContext domainContext )
{
    this.DisposeContext = domainContext == null;
    this.DomainContext = domainContext ?? new PrincipalContext( ContextType.Domain );
}

...

private UserPrincipal FindUser( string userName )
{
    using (PrincipalSearcher userSearcher = new PrincipalSearcher())
    {
        UserPrincipal userFilter = new UserPrincipal( this.DomainContext );
        userFilter.SamAccountName = userName;
        userSearcher.QueryFilter = userFilter;

        // Replace the searcher with one directly associated with the context to ensure that any changes
        // made elsewhere will be reflected if we call the search immediately following the change.  This
        // is critical in the integration tests.
        var searcher = userSearcher.GetUnderlyingSearcher() as DirectorySearcher;
        searcher.SearchRoot = new DirectoryEntry( @"LDAP://" + this.DomainContext.ConnectedServer + @"/dc=iowa,dc=uiowa,dc=edu" );

        return userSearcher.FindOne() as UserPrincipal;
    }
}

...

private void Dispose( bool disposing )
{
    if (!this.disposed)
    {
        if (disposing)
        {
            if (this.DisposeContext && this.DomainContext != null)
            {
                this.DomainContext.Dispose();
                this.DomainContext = null;
            }
        }
        this.disposed = true;
    }
}
tvanfosson