【问题标题】:Mock new objects in empty constructor在空构造函数中模拟新对象
【发布时间】:2021-12-08 04:47:39
【问题描述】:

我目前正在使用 Java 11 开发 AWS Lambda。它要求我的处理程序实现有一个空的构造函数。我的处理程序看起来像这样

public class ApiKeyHandler {

  private final SecretsManagerClient secretsManagerClient;

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

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

和依赖工厂类

public class DependencyFactory {

    private DependencyFactory() {}

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


}

现在,当我尝试为此编写单元测试时,我无法在构造函数中模拟对象。有什么方法可以模拟它吗?

我试过了

@Mock SecretsManagerClient secretsManagerClient;
@InjectMocks ApiKeyHandler handler;

但没有运气。谢谢

【问题讨论】:

    标签: java unit-testing junit mockito


    【解决方案1】:

    看起来你有几个选择:

    1. 您可以添加另一个带有要注入的参数的构造函数。从测试的角度来看,这既简单又干净,但毕竟您将拥有仅用于测试的生产代码(在本例中为此构造函数)。 一般来说,我不提倡这种方法,尽管我知道这里存在技术限制。
    2. 您可以模拟依赖工厂。由于调用是静态的,您最终可能会使用实际上可以模拟静态调用的 PowerMock / PowerMockito。维护这种方法可能会变得非常痛苦,现在通常不鼓励这种方法。
    3. 您可以重写 DependencyFactory,以便可以使用某种模拟实现对其进行配置(这将允许指定模拟依赖项):
    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();
       }
    
    }
    

    使用这种方法,您必须预先配置依赖工厂,以便在测试中运行时它会“知道”它应该在测试模式下运行。

    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(...);
        }
    } 
    

    现在这种方法存在与“1”相同的缺点,但至少您在工厂中拥有“仅用于测试”的代码,而不是在所有 lambda 函数中(我假设您有很多,否则可能第一种方法是万恶之最)。

    另一个可能的缺点是DependencyFactory 的同一个实例(使用共享静态模拟模式)将在测试之间共享,因此您最终可能会在测试后“重置”所有相关的模拟。

    同样,这些都是复杂的,因为在您提供的形式中,由于技术限制,无法在构造函数中提供依赖注入。

    【讨论】:

      【解决方案2】:

      添加第二个接受参数的构造函数:

      public ApiKeyHandler(SecretsManagerClient client) {
        secretsManagerClient = client;
      }
      
      public ApiKeyHandler() {
        this(DependencyFactory.secretsManagerClient());
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-11-24
        • 1970-01-01
        • 2018-04-29
        • 1970-01-01
        • 2016-11-19
        • 2015-07-21
        • 1970-01-01
        相关资源
        最近更新 更多