Liath
Liath

Reputation: 10191

How to verify action passed into function was called?

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

Answers (1)

Patrick Quirk
Patrick Quirk

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

Related Questions