Reputation: 1988
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
Upvotes: 2
Views: 2063
Reputation: 107247
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:
Entities
class (presumably an ORM artifact like an EF DbSet
or a Linq DataContext
) into a factory, via an interface.IConnectionHelper
dependency can be moved into the factory.Authenticator
and its lifespan also managed by your IoC container. The Logger
and EntitiesFactory
can both be injectedYou 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); ...
}
Upvotes: 6