首先,您的测试不会按预期运行,因为您的切换类捕获了运行时异常,而IllegalStateException 是运行时异常,所以它永远不会被抛出。
其次,Spock 不能模拟 Java 类的静态方法,只能模拟 Groovy 类。
因此,如果您不想在 Spock 中摆弄 PowerMock - 模拟静态方法总是难闻的 - 您仍然可以选择通过使功能管理器可通过包范围注入来使切换类更具可测试性setter 方法,然后从测试中使用该方法。试试这个例子:
package de.scrum_master.stackoverflow;
import org.togglz.core.Feature;
import org.togglz.core.annotation.EnabledByDefault;
import org.togglz.core.context.FeatureContext;
import org.togglz.core.manager.FeatureManager;
import org.togglz.core.metadata.FeatureMetaData;
import org.togglz.core.repository.FeatureState;
public enum PocToggle implements Feature {
@EnabledByDefault
USE_MY_FEATURE;
private FeatureManager customFeatureManager;
void setFeatureManager(FeatureManager featureManager) {
this.customFeatureManager = featureManager;
}
public boolean isActive() {
FeatureManager featureManager = customFeatureManager != null
? customFeatureManager
: FeatureContext.getFeatureManager();
try {
return featureManager.isActive(this);
} catch (RuntimeException ignored) {
System.err.println(String.format("Failed to retrieve feature '%s' state", this.name()));
FeatureMetaData metaData = featureManager.getMetaData(this);
FeatureState featureState = metaData.getDefaultFeatureState();
return featureState.isEnabled();
}
}
}
package de.scrum_master.stackoverflow
import org.junit.Rule
import org.togglz.junit.TogglzRule
import org.togglz.testing.TestFeatureManager
import spock.lang.Specification
import static PocToggle.USE_MY_FEATURE
class PocToggleTest extends Specification {
@Rule
TogglzRule toggleRule = TogglzRule.allEnabled(PocToggle.class)
def "Feature is active when enabled"() {
when:
toggleRule.enable(USE_MY_FEATURE)
then:
USE_MY_FEATURE.isActive()
}
def "Feature is inactive when disabled"() {
when:
toggleRule.disable(USE_MY_FEATURE)
then:
!USE_MY_FEATURE.isActive()
}
def "Feature defaults to active upon feature manager error"() {
setup: "inject error-throwing feature manager into Togglz rule"
def featureManagerSpy = Spy(TestFeatureManager, constructorArgs: [PocToggle]) {
isActive(_) >> { throw new IllegalStateException() }
}
when: "feature is disabled and feature manager throws an error"
toggleRule.disable(USE_MY_FEATURE)
USE_MY_FEATURE.featureManager = featureManagerSpy
then: "feature is reported to be active by default"
USE_MY_FEATURE.isActive()
cleanup: "reset Togglz rule feature manager"
USE_MY_FEATURE.featureManager = null
}
}
运行最后一个测试,你会看到如预期的日志消息Failed to retrieve feature 'USE_MY_FEATURE' state。我的测试覆盖率工具也显示它有效:
2018-01-17 更新:使用 PowerMock 的解决方案变体(使用 1.6.6 和 1.7.3 测试)
好的,我需要 PowerMock 是出于另一个原因,并很快让您的代码进行了测试。
免责声明:我确实更喜欢上面的第一个解决方案,即重构依赖注入而不是使用 PowerMock 的肮脏技巧,但是对于它的价值,这里是如何做到的。 p>
Java类又去掉了依赖注入,应该和OP的原代码一样。
package de.scrum_master.stackoverflow;
import org.togglz.core.Feature;
import org.togglz.core.annotation.EnabledByDefault;
import org.togglz.core.context.FeatureContext;
import org.togglz.core.manager.FeatureManager;
import org.togglz.core.metadata.FeatureMetaData;
import org.togglz.core.repository.FeatureState;
public enum PocToggle implements Feature {
@EnabledByDefault
USE_MY_FEATURE;
public boolean isActive() {
FeatureManager featureManager = FeatureContext.getFeatureManager();
try {
return featureManager.isActive(this);
} catch (RuntimeException ignored) {
System.err.println(String.format("Failed to retrieve feature '%s' state", this.name()));
FeatureMetaData metaData = featureManager.getMetaData(this);
FeatureState featureState = metaData.getDefaultFeatureState();
return featureState.isEnabled();
}
}
}
请参阅PowerMock wiki 了解说明。顺便说一句,Sputnik 是 Spock 的 JUnit 跑步者。
package de.scrum_master.stackoverflow
import org.junit.Rule
import org.junit.runner.RunWith
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.modules.junit4.PowerMockRunnerDelegate
import org.spockframework.runtime.Sputnik
import org.togglz.core.context.FeatureContext
import org.togglz.junit.TogglzRule
import org.togglz.testing.TestFeatureManager
import spock.lang.Specification
import static PocToggle.USE_MY_FEATURE
import static org.powermock.api.mockito.PowerMockito.mockStatic
import static org.powermock.api.mockito.PowerMockito.when
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([FeatureContext.class])
class PocToggleTest extends Specification {
@Rule
TogglzRule toggleRule = TogglzRule.allEnabled(PocToggle.class)
// ...
def "Feature defaults to active upon feature manager error (power-mocked)"() {
setup: "inject error-throwing feature manager into Togglz rule"
def featureManagerSpy = Spy(TestFeatureManager, constructorArgs: [PocToggle]) {
isActive(_) >> { throw new IllegalStateException() }
}
mockStatic(FeatureContext)
when(FeatureContext.getFeatureManager()).thenReturn(featureManagerSpy)
when: "feature is disabled and feature manager throws an error"
toggleRule.disable(USE_MY_FEATURE)
then: "feature is reported to be active by default"
USE_MY_FEATURE.isActive()
}
}
更新 2 (2018-01-19): 我想再提一个问题:也许您注意到我使用 Spy 而不是 Stub 或 Mock。这是因为您的代码捕获了功能管理器 (FM) 抛出的异常,但随后在 catch 块中再次使用了 FM。这可能很危险,因为如果 FM 由于网络或数据库故障而损坏,它也可能在您下次调用时失败。因此,虽然测试为您提供了 100% 的代码覆盖率,但它并不能保证您的应用程序在生产环境中的行为符合预期,或者您正在测试正确的东西。