Testing FnProject Functions 说明了如何使用
FnTestingRule
模拟函数调用并查询其结果。该部分按预期工作。但是,我也在尝试使用 Mockito 来验证该函数在调用过程中是否使用某些参数调用了某个协作者;事实上,有很多调用请求和协作者期望的元组 T 我想进行相同的测试。我想不出一个好的方法。
问题是组成 FnProject 单元测试的模式是向框架(JUnit
@Rule
)提供函数类,框架通过反射实例化该类,使用默认的无参数构造函数,在专有类加载器。因此,对于每个 T,我必须以某种方式在测试工具和临时函数实例(跨类加载器)之间与模拟协作者或 T 进行通信,并且我必须在不求助于无参数构造函数的情况下完成此操作.
使用
FnTestingRuleFeature
我看不到任何接缝。
到目前为止,最好但仍然很丑陋的方法是围绕静态最终模拟协作者创建一个嵌套包装类,并在每次测试期间与
FnTestingRule::addSharedClass
共享包装类。如果我尝试在测试类级别创建静态模拟协作者,我最终不得不与 FnTestingRule
共享更多类以使其工作。而且我从来不知道要分享什么才能让 AtomicReference
包装器工作而不是自定义包装器。
@RunWith( Parameterized.class )
public class TestHarness {
public static class MockCollaboratorWrapper {
// a class level mock that has to be reset between tests is a
// nauseating code smell; but how else to convey between a
// reflection-instantiated TestableFunction (for actual invocations)
// and the harness (for calls to verify)?
private static final Collaborator MOCK_COLLABORATOR = Mockito.mock( Collaborator.class );
public Collaborator getCollaborator() { return MOCK_COLLABORATOR; }
}
// this class is what we pass to the FnTestingRule so that we can
// inject a mock collaborator despite reflection calling no-arg constructor
private static class TestableFunction extends MyFunction {
public TestableFunction() {
// protected constructor for testing, allows parameters;
// this type of testing seam in MyFunction smells bad too
super( MockCollaboratorWrapper.getCollaborator() );
}
}
@Parameters
public static Iterable< TestCaseTuple > cases() {
return List.of(
// all the test case combinations go here...
);
}
// JUnit runner runs one test for each of the above cases
// injecting the particular TestCaseTuple into this field
@Parameter
public TestCaseTuple tctEach;
@Rule
FnTestingRule testing = FnTestingRule.createDefault();
@Test
public void runOneTest() {
// prepare static mock for this test, ick
Mockito.reset( MockCollaboratorWrapper.getCollaborator() );
Mockito.doReturn( tctEach.getCollabResult() ).
when( MockCollaboratorWrapper.getCollaborator() ).myMethod( Mockito.any() );
// this is the evil magic which shares the mock instance
// across the ClassLoader chasm
testing.addSharedClass( MockCollaboratorWrapper.class );
// run the test as shown at FnProject docs
testing.givenEvent().
withHeader( "Fn-Http-Request-Uri", tctEach.getURL() ).
// ...
enqueue();
testing.thenRun( TestableFunction.class, "handleRequest" );
FnResult result = testing.getOnlyResult();
// check status code
assertEquals(
Integer.valueOf( tctEach.getExpectedStatus() ),
result.getHeaders().get( "Fn-Http-Status" ).
map( Integer::valueOf ).
orElseGet( result.getStatus()::getCode )
);
// check other things about the result...
// check the interaction with the collaborator
// we could set up the stubbing to verify this directly, but then the
// failure would happen inside the function invocation and get
// translated into console output and a 502 result code
Mockito.verify( MockCollaboratorWrapper.getCollaborator() ).
myMethod( tctEach.getCollabArg() );
}
}
当然有更好的方法吗?你怎么说,无限循环者?