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:
- Separate out the concern of creation of the
Entities
class (presumably an ORM artifact like an EFDbSet
or a LinqDataContext
) into a factory, via an interface. - The
IConnectionHelper
dependency can be moved into the factory. - 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. TheLogger
andEntitiesFactory
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); ...
}