开发者

Unit testing object construction/initialization

开发者 https://www.devze.com 2023-01-04 18:48 出处:网络
I have a class Foo that uses another class Bar to do some stuff. I\'m trying to do test driven development, and therefore am writing unit tests for Foo to make sure it calls the appropriate methods o

I have a class Foo that uses another class Bar to do some stuff.

I'm trying to do test driven development, and therefore am writing unit tests for Foo to make sure it calls the appropriate methods on Bar, and for this purpose I'm using dependency injection and mocking Bar (using Rhino Mocks).

E.g. (in C#):

class Foo
{
  private IBar bar= null;

  public Foo(IBar bar)
  {
    this.bar= bar;
  }

  public void DoSomething()
  {
    bar.DoIt();
  }
}

class FooTests
{
  ...
  void DoSomethingCallsBarsDoItMethod()
  {
    IBar mockBar= MockRepository.GenerateMock<IBar>();
    mockBar.Expect(b=>b.DoIt());    
    Foo foo= new Foo(mockBar);
    foo.DoSomething();
    mockBar.VerifyAllExpectations();
  }
}

This all seems to be fine, but I actually want Bar to be configured with a particular parameter, and was going to have this parameter passed in via Foo's constructor. E.g. (in C#):

public Foo(int x)
{
  this.bar = new Bar(x);
}

I'm not sure which is the best way to change this to be more easily testable. One options I can think of involves moving the initialization of Bar out of its constructor, e.g.

public Foo (IBar bar, int x)
{
  this.bar= bar;
  this.bar.Initialize(x);
}

I feel that this is making Bar harder to use though.

I suspect there may be some kind of solution that would involve using an IoC container configured to inject Foo with a mock IBar and also provide access to the created mock for expectation validation, but I feel this would be making Foo unnecessarily c开发者_如何学JAVAomplex. I'm only using dependency injection to allow me to mock the dependencies for testing, and so am not using IoC containers at present, just constructing dependencies in a chained constructor call from the default constructors (although I realize this is creating more untested code) e.g.

public Foo() :
  this(new Bar())
{
}

Can anyone recommend the best way to test dependent object construction/initialization?


It seems to me that you are letting an implementation detail leak through the API design. IBar shouldn't know anything about Foo and its requirements.

Implement Bar with Constructor Injection just like you did with Foo. You can now make them share the same instance by supplying it from the outside:

var x = 42;
IBar bar = new Bar(x);
Foo foo = new Foo(bar, x);

This would require your Foo constructor to look like this:

public class Foo
{
    private readonly int x;
    private readonly IBar bar;

    public Foo(IBar bar, int x)
    {
        if (bar == null)
        {
            throw new ArgumentNullException("bar");
        }

        this.bar = bar;
        this.x = x;
    }
}

This allows Foo to deal with any implementation of IBar without letting the the implementation details leak through. Separating IBar from x can be viewed as an implementation of the Interface Segregation Principle.

In DI Container terminology, x can be viewed as being Singleton-scoped (not to be confused with the Singleton design pattern) because there is only a single instance of x across many different components. The instance is shared and it is strictly a lifetime management concern - not an API design concern.

Most DI Containers have support for defining a service as a Singleton, but a DI Container is not required. As the above example shows, you can also wire shared services up by hand.

In the cases where you don't know the value of x until run-time, Abstract Factory is the universal solution.


Define this variable x in your dependency injection framework, and inject it to both Foo and Bar.


How is Foo created? Is it coming from an inversion of control container? If so you may be able to use features in your IoC container to configure these objects.

If not, just bite the bullet and add a method Foo.Initializer(parameter) which calls Bar.Initialize(parameter) or perhaps takes an instance of Bar.

I haven't been doing unit testing that much longer then you, but I've found I don't do things in constructors anymore. It just gets in the way of testing. A constructor takes the objects dependencies, then its done.


This is more a question about design. Why does Foo need Bar initialized with a particular value of x? Potentially the class boundaries are not defined optimally. Maybe Bar shouldn't encapsulate the value of x at all, just take it as a parameter in the method call:

class Foo
{
  private IBar bar= null;
  private int x;

  public Foo(IBar bar, int x)
  {
    this.bar= bar;
    this.x = x;
  }

  public void DoSomething()
  {
    bar.DoIt(x);
  }
}

If for some reason you can't change the design I think I would just have an assertion in the constructor:

class Foo
{
  private IBar bar= null;

  public Foo(IBar bar)
  {
    if(bar.X != 42) 
    {
      throw new ArgumentException("Expected Bar with X=42, but got X="+bar.X);
    }
    this.bar= bar;
  }

  public void DoSomething()
  {
    bar.DoIt();
  }
}

This example also shows that you need to have some integration tests to make sure all the individual classes are correctly integrated with each other :)


I'd just overload the constructor, have one that accepts x, another that accepts a Bar.

This makes your object easier to use and easier to test.

//containing class omitted
public Foo(Bar bar) {
    this.bar = bar;
}

public Foo(int x) {
    this(new DefaultBar(x));
}

This is what constructor overloading was invented for.

0

精彩评论

暂无评论...
验证码 换一张
取 消