I hope this isn't too vague, but I'm just learning about unit testing with fakes using NUnit and Moq. I understand the concepts and I am having no trouble writing tests for logical methods that perform a simple task, like manipulate some values or call a faked service. But I'm scratching my head a bit trying to figure out how to test something like the following, which is a method that needs to do a data call and has several dependencies.

This is a class I have to authenticate users against a database:

class Authenticator: IAuthenticator
{

    private IConnectionHelper _connHelper;

    public Authenticator(IConnectionHelper connHelper)
    {
        _connHelper = connHelper;
    }

    public string UserId { get; set; }
    public string Password { get; set; }
    public UserModel User { get; private set; }

    public bool ValidateUser()
    {

            if (string.IsNullOrEmpty(UserId))
                return false;

            if (string.IsNullOrEmpty(Password))
                return false;

            using (Entities db = new Entities(_connHelper))
            {
                MD5 md5 = MD5.Create();
                byte[] inputBytes = Encoding.ASCII.GetBytes(this.Password);
                byte[] hash = md5.ComputeHash(inputBytes);
                string md5Hash = BitConverter.ToString(hash).ToUpper().Replace("-", string.Empty);

                var query = from u in db.Users
                            where u.UserId.ToUpper().Trim() == this.UserId.ToUpper()
                            where u.CPassword.Trim() == md5Hash
                            select u;

                this.User = query.FirstOrDefault();
            }



        if (this.User == null)
        {
            Log.LogLine(TraceLevel.Verbose, string.Format("Authentication failed for user '{0}'", this.UserId));
            return false;
        }
        else
        {
            Log.LogLine(TraceLevel.Verbose, string.Format("Successfully authenticated user '{0}'", this.UserId));
            return true;
        }
    }


}

I want to create a test fixture for this, but I'm unsure of how to handle it.

I can mock the IConnectionHelper, and test that the method will fail if the UserId or Password are null/empty, but that's as far as I can get. If IConnectionHelper is fake, then I obviously won't be able to test the database stuff, which I'm not sure I'm supposed to do anyway. Can someone please shed some light on best practices here?

EDIT

The accepted answer from StuartLc definitely got me going in the right direction. I did have to do a little extra setup work for Entity Framework, basically using this as a reference: http://msdn.microsoft.com/en-us/data/dn314429.aspx

有帮助吗?

解决方案

As per Ryan's comment, your code is too tightly coupled to the dependencies Entities and Log to be easy to unit test (without having to resort to Moles or Fakes).

As a general rule, use of new to create any substantial dependency, or use of static methods like Log are generally the culprits preventing isolated unit testing with frameworks like Moq.

What I would suggest is refactoring the code as follows:

  1. Separate out the concern of creation of the Entities class (presumably an ORM artifact like an EF DbSet or a Linq DataContext) into a factory, via an interface.
  2. The IConnectionHelper dependency can be moved into the factory.
  3. The static logger should also be stripped into an interface, and an instance should also be injected into your Authenticator and its lifespan also managed by your IoC container. The Logger and EntitiesFactory can both be injected

You should wind up with something like the below:

  public interface IEntitiesFactory
  {
     Entities Create();
  }

  public interface ILog
  {
     void LogLine(TraceLevel level, string message);
  }

  class Authenticator : IAuthenticator
  {
     private readonly IEntitiesFactory _entitiesFactory;
     private readonly ILog _log;

     public Authenticator(IEntitiesFactory entitiesFactory, ILog log)
     {
        _entitiesFactory = entitiesFactory;
        _log = log;
     }

     public string UserId { get; set; }
     public string Password { get; set; }
     public UserModel User { get; private set; }

     public bool ValidateUser()
     {
        if (string.IsNullOrEmpty(UserId))
           return false;

        if (string.IsNullOrEmpty(Password))
           return false;

        using (var db = _entitiesFactory.Create())
        {
           MD5 md5 = MD5.Create();
           byte[] inputBytes = Encoding.ASCII.GetBytes(this.Password);
           byte[] hash = md5.ComputeHash(inputBytes);
           string md5Hash = BitConverter.ToString(hash).ToUpper().Replace("-", string.Empty);

           var query = from u in db.Users
                       where u.UserId.ToUpper().Trim() == this.UserId.ToUpper()
                       where u.CPassword.Trim() == md5Hash
                       select u;

           this.User = query.FirstOrDefault();
        }

        if (this.User == null)
        {
           _log.LogLine(TraceLevel.Verbose, string.Format("Authentication failed for user '{0}'", this.UserId));
           return false;
        }
        else
        {
           _log.LogLine(TraceLevel.Verbose, string.Format("Successfully authenticated user '{0}'", this.UserId));
           return true;
        }
     }

, where you can now Mock out the Entities Factory and the logger, and now provide fake Users data for the found / not found scenarios, and verify that the right stuff was sent to logger, etc, viz

 [Test]
 public void SomeTest()
 {
    var mockFactory = new Mock<IEntitiesFactory>();
    var mockEntities = new Mock<Entities>();
    var fakeUsers = new List<UserModel>
       {
          new UserModel
             {
                UserId = "Bob",
                CPassword = "TheHashOfSecret"
             }
       };
    mockEntities.SetupGet(_ => _.Users).Returns(fakeUsers.AsQueryable());
    mockFactory.Setup(_ => _.Create()).Returns(mockEntities.Object);
    var mockLog = new Mock<ILog>();
    var sut = new Authenticator(mockFactory.Object, mockLog.Object)
       {
          UserId = "Bob",
          Password = "Secret"
       };
    Assert.DoesNotThrow(() => sut.ValidateUser());
    Assert.IsNotNull(sut.User);
    mockLog.Verify(_ => _.LogLine(), Times.Once); ...
 }
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top