在空构造函数中模拟新对象
Mock new objects in empty constructor
我目前正在使用 Java11 开发 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(/../);
}
}
和依赖工厂class
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;
但运气不好。谢谢
添加第二个接受参数的构造函数:
public ApiKeyHandler(SecretsManagerClient client) {
secretsManagerClient = client;
}
public ApiKeyHandler() {
this(DependencyFactory.secretsManagerClient());
}
看起来你有几个选择:
- 您可以添加另一个带有要注入参数的构造函数。从测试的角度来看,这既简单又干净,但毕竟您将拥有仅用于测试的生产代码(在本例中为此构造函数)。
一般来说我不提倡这种方法,虽然我知道这里有技术限制。
- 您可以模拟依赖工厂。由于调用是静态的,您最终可能会使用实际上可以模拟静态调用的 PowerMock / PowerMockito。这是一种维护起来非常痛苦的东西,一般来说,现在不鼓励这种方法。
- 您可以重写 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
的相同实例(使用共享静态模拟模式)将在测试之间共享,因此您可能会在测试后“重置”所有相关模拟。
同样,这些都是复杂的,因为在您提供的形式中,由于技术限制,无法在构造函数中提供依赖项注入。
我目前正在使用 Java11 开发 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(/../);
}
}
和依赖工厂class
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;
但运气不好。谢谢
添加第二个接受参数的构造函数:
public ApiKeyHandler(SecretsManagerClient client) {
secretsManagerClient = client;
}
public ApiKeyHandler() {
this(DependencyFactory.secretsManagerClient());
}
看起来你有几个选择:
- 您可以添加另一个带有要注入参数的构造函数。从测试的角度来看,这既简单又干净,但毕竟您将拥有仅用于测试的生产代码(在本例中为此构造函数)。 一般来说我不提倡这种方法,虽然我知道这里有技术限制。
- 您可以模拟依赖工厂。由于调用是静态的,您最终可能会使用实际上可以模拟静态调用的 PowerMock / PowerMockito。这是一种维护起来非常痛苦的东西,一般来说,现在不鼓励这种方法。
- 您可以重写 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
的相同实例(使用共享静态模拟模式)将在测试之间共享,因此您可能会在测试后“重置”所有相关模拟。
同样,这些都是复杂的,因为在您提供的形式中,由于技术限制,无法在构造函数中提供依赖项注入。