Reputation: 10191
I'm struggling with a unit test using Moq to isolate the BL from the DataAccess. Consider the following business logic:
public class MyBusinessLogic
{
private IDataAccess _dataAccess;
public MyBusinessLogic(IDataAccess dataAccess)
{
_dataAccess = dataAccess;
}
public string SendEmail(string username)
{
var emailAddress = _dataAccess.RunCommand<CommandDetails>("GetEmailAddressForUser", AddParametersToGetEmailAddress, new CommandDetails{Username = username});
// do some send email stuff...
return string.Concat("Email sent to ", emailAddress);
}
private void AddParametersToGetEmailAddress(IDbCommand command, CommandDetails details)
{
var p = command.CreateParameter();
p.ParameterName = "username";
p.Value = details.Username;
command.Parameters.Add(p);
}
}
A set of parameter details
public class CommandDetails
{
public string Username {get; set;}
}
And a DataAccess interface
public interface IDataAccess
{
string RunCommand<TCommandDetails>(string command, Action<IDbCommand, TCommandDetails> addParameters, TCommandDetails details);
}
I have a test
var username = @"joebloggs";
var mock = new Mock<IDataAccess>();
mock.Setup(x => x.RunCommand<CommandDetails>(
It.Is<string>(commandString => commandString == "GetEmailAddressForUser"),
It.IsAny<Action<IDbCommand,CommandDetails>>(),
It.Is<CommandDetails>(details => details.Username == username)))
.Returns("[email protected]");
var businessLogic = new MyBusinessLogic(mock.Object);
var message = businessLogic.SendEmail(username);
Assert.AreEqual("Email sent to [email protected]", message);
This is great, it proves that the SP was called and the email was sent to the address returned.
However, it does not prove that the AddParametersToGetEmailAddress
method was called. For all we know the SP is being called with no parameters.
How can I modify my setup to only return the email address the username parameter has been set on the command?
Upvotes: 0
Views: 512
Reputation: 23747
AddParametersToGetEmailAddress
won't be called because you're mocking the method (RunCommand
) that would call it.
To achieve what (I think) you want you can add a callback to the method setup that will invoke your action, and then check the value of the CommandDetails
afterwards:
var mockDbCommand = new Mock<IDbCommand>();
mock.Setup(x => x.RunCommand<CommandDetails>(
It.Is<string>(commandString => commandString == "GetEmailAddressForUser"),
It.IsAny<Action<IDbCommand,CommandDetails>>(),
It.Is<CommandDetails>(details => details.Username == username)))
.Callback<string, Action<IDbCommand,CommandDetails>, CommandDetails((s,a,d) => WrapAction(mockDbCommand.Object, d, a))
.Returns("[email protected]");
...
void WrapAction(IDbCommand dbCommand, CommandDetails commandDetails, Action<IDbCommand,CommandDetails> action)
{
// Call the action, which will be AddParametersToGetEmailAddress
action(dbCommand, commandDetails);
// Assert that dbCommand has the username parameter
}
Note that you'll need to create mockDbCommand
to pass to the action.
Upvotes: 1