简体   繁体   中英

Unit testing void method that creates a new object

I have a method like the following:

public void ExecuteSomeCommand()
{
    new MyCommand( someInt, SomeEnum.EnumValue ).Execute();
}

I'd like to test that the enum value that is passed in to the constructor of the ICommand object I'm creating is the correct value. Is there any way I can do this with Rhino.Mocks?

Option 1: Use a Seam

The easiest way is to refactor that method with a seam:

public void ExecuteSomeCommand()
{
    this.CreateCommand(someInt, SomeEnum.EnumValue).Execute();
}

// Your seam
protected virtual ICommand CreateCommand(int someInt, 
    SomeEnum someEnum)
{
    return new MyCommand(someInt, SomeEnum.EnumValue);
}

This way you can intercept the creation of the 'new' operator by extending this class. When doing this by hand, it might look like this:

public FakeSomeService : SomeService
{
    public int SomeInt;
    public SomeEnum SomeEnum;

    protected override Command CreateCommand(int someInt, 
        SomeEnum someEnum)
    {
        this.SomeInt = someInt;
        this.SomeEnum = someEnum;
        return new FakeCommand();
    }

    private sealed class FakeCommand : Command
    {
        public override void Execute() { }
    }
}

This fake class can be used in your test methods.


Option 2: Separate behavior and data

A better way would be to separate the data from the behavior. You command has both data (the message) and the behavior (handling that message). If you are allowed to do such a change in your code base: separate this, for instance by defining commands and command handlers. Here is an example:

// Define an interface for handling commands
public interface IHandler<TCommand>
{
    void Handle(TCommand command);
}

// Define your specific command
public class MyCommand
{
    public int SomeInt;
    public SomeEnum SomeEnum;
}

// Define your handler for that command
public class MyCommandHandler : IHandler<MyCommand>
{
    public void Handle(MyCommand command)
    {
        // here your old execute logic
    }
}

Now you can use dependency injection to inject a handler into the class you wish to test. This class will now look like this:

public class SomeService
{
    private readonly IHandler<MyCommand> handler;

    // Inject a handler here using constructor injection.
    public SomeService(IHandler<MyCommand> handler)
    {
        this.handler = handler;
    }

    public void ExecuteSomeCommand()
    {
        this.handler.Handle(new MyCommand
        {
            SomeInt = someInt,
            SomeEnum = someEnum
        });
    }
}

Since you now separated the data from the behavior, it will be very easy to create a fake command handler (or create it using Rhino mocks) that checks if the correct command was sent to the handler. Manually this would look like this:

public class FakeHandler<TCommand> : IHandler<TCommand>
{
    public TCommand HandledCommand { get; set; }

    public void Handle(TCommand command)
    {
        this.HandledCommand = command;
    }
}

This fake handler can be reused throughout your unit testing project. A test using this FakeHandler could look like this:

[TestMethod]
public void SomeTestMethod()
{
    // Arrange
    int expected = 23;

    var handler = new FakeHandler<MyCommand>();

    var service = new SomeService(handler);

    // Act
    service.ExecuteSomeCommand();

    // Assert
    Assert.AreEqual(expected, handler.HandledCommand.SomeInt);
}

Separating the data from the behavior not only makes your application more testable. It makes your application more resilient to change. For instance, cross-cutting concerns can be added to the execution of commands, without the need to make changes to any handler in the system. Because the IHandler<T> is an interface with a single method, it is very easy to write a decorator that can wrap every handler and add things like logging, audit trailing, profiling, validation, transaction handling, fault tolerance improvents, etc. You can read more about it in this article .

None that I'm aware of. The closest thing that comes to my mind is to use a factory, then create a StrictMock of that factory. Something like that:

readonly ICommandFactory factory;

public Constructor(ICommandFactory factory)
{
    this.factory = factory;
}
public void ExecuteSomeCommand()
{
    factory.Create( someInt, SomeEnum.EnumValue ).Execute();
}

then, you can place expectations on invokation to Create() .

HTH

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM