Question

I'm attempting to design a class that is to be instantiated through the use of a static method, something like newInstance(param1, param2). The reason behind this is that 2 of the 4 parameters it takes are from the same package, but I want to hide them from the public.

In order to achieve this, I made the constructor private. However, I just realize that I am now unable to pass the 2 parameters on my tests.

It goes like this

class Foo {
    private constructor(p1, p2, p3, p4) {}

    public static newInstance(p1, p2) {
        p3 = new p3;
        p4 = new p4;
        return new static(p1, p2, p3, p4);
    }
}

Obviously my design is wrong, but I can't think of a way to abstract the existence of p3 and p4 to other developers, while still being able to pass them as dependency in order to apply dependency inversion.

What design can I use to achieve this? Maybe I should just create a FooFactory and document that they're supposed to use that...


I'm basically writing an SDK for an REST API. These REST API is not designed by me nor my company (so I can't make the REST API follow industry standards), and it has a very complicated authentication mechanism. It is complicated enough that I want to achieve the following:

  1. Break it down into little testable components, so that it's easier to maintain, and the other developers are able to work with just the endpoints the SDK expose, which encapsulate the domain model.
  2. Abstract the construction of the dependency graph for the SDK, so that they can just use the CompanyClient class and call business domain methods like getCustomerList.
  3. Completely abstract the construction of the customized headers, parameters, etc... of the HTTP request.

I agree this work as a unit so it make sense just to test the CompanyAPI class by itself; However, that would make it difficult to maintain the sub-mechanisms that are needed to make it work.

In summary, by breaking it down into smaller pieces, it's easy to test that every mechanism works according to the specification.

For this reason I want just to expose 2 parameters for the library users. They don't need to know the underlying implementation. However, I DO need to test and since the dependencies include a HTTP client, I need to inject mock objects.

Was it helpful?

Solution

If you are using c#, you can use this "friend" approach:

Add another (possibly parameterless) constructor to the class solely for your test project.

"Hide" the constructor from other developers by making it internal. You can then make the constructor visible to your test project by using the InternalsVisibleToAttribute.

[assembly:InternalsVisibleTo("MyUnitTestProject")]

class Foo {
    internal constructor(p1, p2, p3, p4) {}

    public static newInstance(p1, p2) {
        p3 = new p3;
        p4 = new p4;
        return new static(p1, p2, p3, p4);
    }
}

If you are not using c#, or would rather not use "friend" attributes:

  1. Create a non-static factory for this class, along with an interface for the factory, e.g. IFooFactory.

    interface IFooFactory { Foo newInstance(p1, p2); }
    
    class FooFactory : IFooFactory
    {
        public Foo newInstance(p1, p2)
        {
            return new Foo(p1, p2, new p3(), new p4());
        }
    }
    
  2. In production code, inject the non-static factory into any class that needs to be able to instantiate a Foo. Use your injection container to ensure that there is only one instance of the factory per thread/process/whatever is appropriate for your application.

    class SomeOperation
    {
        private readonly IFooFactory _fooFactory;
    
        public SomeOperation(IFooFactory fooFactory)
        {
            _foofactory = fooFactory;
        }
    }
    
    
    class CompositionRoot
    {
        public RegisterDependencies(IContainer container)
        {
            container.RegisterType<IFooFactory, FooFactory>().instancePerProcess();  //Syntax may vary
            container.RegisterType<SomeOperation, SomeOperation>();
        }
    }
    
  3. Replace any call to

    Foo.newInstance(p1, p2)
    

    with

    _fooFactory.newInstance(p1, p2);
    
  4. In test code, write your own test factory that conforms to IFooFactory and inject it instead of the production factory. Your test factory may be able to get away with supplying dummy p3 and p4 or you can stub those too.

OTHER TIPS

Based on what you want, it feels Foo, along with p3 and p4 all form a unit. As such, they should be tested together as a unit. Which means instantiating Foo through the static method is perfectly fine for unit test.

If this results in too complicated tests, then that means your decision to not be able to instantiate Foo with different classes might not be correct one.

Licensed under: CC-BY-SA with attribution
scroll top