Home > database >  Mock new objects in empty constructor
Mock new objects in empty constructor

Time:12-08

I am currently working on a AWS Lambda using Java 11. It is requiring me for an implementation of the handler to have an empty constructor. My handler looks like this

public class ApiKeyHandler {

  private final SecretsManagerClient secretsManagerClient;

  public ApiKeyHandler() {
    secretsManagerClient = DependencyFactory.secretsManagerClient();
  }

  public void handleRequest(Object event, Context context) {
    //Other codes here
    secretsManagerClient.getSecret(/../);
  }
}

And Dependency Factory class

public class DependencyFactory {

    private DependencyFactory() {}

    /**
     * @return an instance of SecretsManagerClient
     */
    public static SecretsManagerClient secretsManagerClient() {
        return SecretsManagerClient.builder()
            .region(/**/)
            .build();
    }


}

Now, when I am trying to write unit test for this I cant mock objects in constructor. Is there a way I can mock it?

I tried

@Mock SecretsManagerClient secretsManagerClient;
@InjectMocks ApiKeyHandler handler;

but no luck. Thank you

CodePudding user response:

Add a second constructor that accepts parameters:

public ApiKeyHandler(SecretsManagerClient client) {
  secretsManagerClient = client;
}

public ApiKeyHandler() {
  this(DependencyFactory.secretsManagerClient());
}

CodePudding user response:

It looks like you have a couple of options:

  1. You can add another constructor with the parameters to inject. This is easy and clean from the test perspective, but after all you'll have the code in production (this constructor in this case) that is used only for tests. In generally I don't advocate this approach, although I understand that there are technology limitations here.
  2. You can Mock the Dependency Factory. Since the call is static, you might end up using PowerMock / PowerMockito that can actually mock static calls. This is something that can turn to be really painful to maintain, in general this approach is discouraged these days.
  3. You can Rewrite the DependencyFactory so that it could be configured with some kind of mock implementation (that will allow to specify mock dependencies):
public interface DependencyFactoryMode {
   SecretsManagerClient secretsManagerClient();  
}

public class RealDependencyFactoryMode implements DependencyFactoryMode {
    public SecretsManagerClient secretsManagerClient() {
        return SecretsManagerClient.builder()
            .region(/**/)
            .build();
  
    }
}
// in src/test/java - test code in short
public class DependencyFactoryTestMode implements DependencyFactoryMode {
    private SecretsManagerClient smc = Mockito.mock(SecretsManagerClient.class);
    public SecretsManagerClient secretsManagerClient() {
       return smc;
    }
    // this will be used in tests
    public SecretsManagerClient getSmcMock() {return smc;}
}

public class DependencyFactory {
   private static DependencyFactoryMode mode;

   static {
      // depending on the configuration, external properties or whatever 
      // initialize in production mode or test mode
      // of course this is the most "primitive" implementation you can probably
      // do better
      if(isTest) {
         mode = new TestDependencyFactoryTestMode();
      } else {
         // this is a default behavior
         mode = new RealDependencyFactoryMode();
      }
   }

   private DependencyFactory() {}
   public static DependencyFactoryMode getMode() {
      return mode;
   }
   public static SecretsManagerClient secretsManagerClient() {
      return mode.secretsManagerClient();
   }

}

With this approach you'll have to pre-configure the dependency factory so that while running in the test it will "know" that it should run in the test mode.

public class Test {
    @Test
    public void test() {
      // DependencyFactoryMode will be used in the test mode
      DependecyFactoryMode testMode = DependencyFactory.getMode(); 
      var smc = testMode.secretsManagerClient();
      Mockito.when(smc.foo()).thenReturn(...);
    }
} 

Now this approach suffers from the same drawback as "1" but at least you have a code "only for tests" only in the factory, rather than in all lambda functions (I assume you have many of them, otherwise probably the first approach will be the least of all evils).

Another possible drawback is that the same instance of DependencyFactory (with the shared static mocking mode) will be shared between the tests, so you might end up "reseting" all the relevant mocks after the test.

Again, these all are complications because in the form that you've presented there is no way to provide a dependency injection in constructor because of the technology limitation.

  • Related