Kiwi breeder
Kiwi breeder

Reputation: 617

How to test object initialisation within a super constructor?

I have a BasePersister that builds a complex persistence client in it's constructor, using Dagger:

public abstract class BasePersister {
   @Getter
   private PersistenceClient client;

   public BasePersister() {
      this.client = DaggerPersistenceClientComponent.create().getClient();
   }

   public abstract void persist(String data);

   protected void helper() {
      System.out.println("I am a helper");
   }
}

The idea is that child persister classes can just extend the base class and perform its persistence logic with the client. An example child class:

public class SpecialPersister extends BasePersister {
   public void persist(String data) {
      // do stuff with the client
      getClient().persist(data);
      // use the shared helper
      helper();
   }
}

Moving the client instantiation within the base class constructor was ideal because in my PersisterFactory, I can simply invoke statements like new SpecialPersister(); the constructor doesn't take any arguments, doesn't need Dagger to instantiate and the factory is completely unaware of any clients.

I'm having trouble testing these child classes and I'm suspecting it has to do with my design choice of secretly instantiating clients within the base constructors.

More specifically, in my SpecialPersisterTest class, I can't do Spy(SpecialPersister) as this invokes the base constructor, which then instantiates my complex clients (giving me an error). I somehow need to mock this super-constructor call so that it doesn't actually invoke client instantiation, which has complex network calls etc.

Ideally, I can do a simple test such as checking:

def "my ideal test"() {
   given:
      specialPersister.persist(validData)
   then:
      1 * specialPersister.getClient()
   and:
      1 * specialPersister.helper()
}

Upvotes: 0

Views: 159

Answers (1)

j1mbl3s
j1mbl3s

Reputation: 1028

Moving the client instantiation within the base class constructor was ideal because in my PersisterFactory, I can simply invoke statements like new SpecialPersister(); the constructor doesn't take any arguments, doesn't need Dagger to instantiate and the factory is completely unaware of any clients.

I'm having trouble testing these child classes and I'm suspecting it has to do with my design choice of secretly instantiating clients within the base constructors.

This design choice is the issue. If you want the code to be testable without making calls on the real client, you will need to be able to stub your client. One option for this is to pass the PersistenceClient in at instantiation.

Since you are using a factory pattern, your factory can provide it without worrying about the details elsewhere in your code. It should know how to create Persister objects, regardless of if it needs to know the details about the client - coupling at this level should be encouraged. You may also want your factory to take the argument, as well, so that a Persister from the factory can be tested.

public abstract class BasePersister {
    private PersistenceClient client;
    public BasePersister(PersistenceClient client) {
        this.client = client;
    }
}
public class SpecialPersister extends BasePersister {
    public SpecialPersister(PersistenceClient client) {
        super(client);
    }
}

public class PersisterFactory {
    // pass in the client once to a PersisterFactory instance
    private PersistenceClient client;
    public PersisterFactory(PersistenceClient client) {
        this.client = client;
    }
    public SpecialPersister createSpecialPersister() {
        return new SpecialPersister(client);
    }
}

// elsewhere
PersisterFactory persisterFactory = new PersisterFactory(DaggerPersistenceClientComponent.create().getClient());
// ...
BasePersister persister = persisterFactory.createSpecialPersister();

Upvotes: 1

Related Questions