这就是我想要做的事情:
def mockSubdirs = []
mockSubdirs << Mock( File ){
getName() >> 'some subdir'
lastModified() >> 2000
}
...
File mockParentDir = Mock( File ){
getName() >> 'parent dir'
eachDir() >> mockSubdirs.iterator() // ??? NB eachDir is a GDK method
// I tried things along these lines:
// listFiles() >> mockSubdirs
// iterator() >> mockSubdirs.iterator()
}
cut.myDirectory = mockParentDir
应用代码如下:
def dirNames = []
myDirectory.eachDir{
dirNames << it.name
}
以上所有内容都在FileNotFoundException
行{...}}上提供myDirectory.eachDir{
后
感谢所有3位回答者提供可能的解决方案。 Kriegaex的代码示例似乎对我不起作用,我不确定为什么。但他建议查看Groovy源代码很棒。所以在NioGroovyMethods.java中我发现eachDir
调用eachFile
看起来像这样:
public static void eachFile(final Path self, final FileType fileType, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") final Closure closure) throws IOException {
//throws FileNotFoundException, IllegalArgumentException {
checkDir(self);
// TODO GroovyDoc doesn't parse this file as our java.g doesn't handle this JDK7 syntax
try (DirectoryStream<Path> stream = Files.newDirectoryStream(self)) {
for (Path path : stream) {
if (fileType == FileType.ANY ||
(fileType != FileType.FILES && Files.isDirectory(path)) ||
(fileType != FileType.DIRECTORIES && Files.isRegularFile(path))) {
closure.call(path);
}
}
}
}
...所以我的第一个想法就是试图模仿Files.newDirectoryStream
。 Files
为final
,因此您必须使用GroovyMock
,因为该方法为static
,您似乎必须使用以下内容:
GroovyMock( Files, global: true )
Files.newDirectoryStream(_) >> Mock( DirectoryStream ){
iterator() >> mockPaths.iterator()
}
...沿着这些方向尝试似乎不起作用......听到有人认为Groovy语言机制中的Files
类不会受此影响,我不会感到惊讶试图模仿...
然后我认为必须在问题的toPath
上调用File
,所以尝试了这个:
File mockParentDir = Mock( File ){
toPath() >> {
println "toPath called"
Mock( Path )
}
}
...此行未打印。好吧,我有点难过:要从Path
获得File
我给它Groovy机制必须使用偷偷摸摸的东西:也许像getAbsolutePath()
...然后创建一个结果Path
中的String
?这需要更多的源代码检查......但如果是这种情况,那么你就不能强迫Groovy使用模拟Path
!
或者......也许其他神秘的Groovy事情在这里发挥作用:元类等?
答案 0 :(得分:2)
这取决于你真正想要测试的内容。以下是一个可能有用的示例:
class DirectoryNameHelper {
/*
* This is silly, but facilitates answering a question about mocking eachDir
*/
List<String> getUpperCaseDirectoryNames(File dir) {
List<String> names = []
dir.eachDir {File f ->
names << f.name.toUpperCase()
}
names
}
}
嘲笑eachDir
的测试。这只是测试被测试的方法调用eachDir并传递一个闭包,它返回每个目录名的大写版本。
import groovy.mock.interceptor.MockFor
import spock.lang.Specification
class EachDirMockSpec extends Specification {
void 'test mocking eachDir'() {
setup:
def mockDirectory = new MockFor(File)
mockDirectory.demand.eachDir { Closure c ->
File mockFile = Mock() {
getName() >> 'fileOne'
}
c(mockFile)
mockFile = Mock() {
getName() >> 'fileTwo'
}
c(mockFile)
}
when:
def helper = new DirectoryNameHelper()
def results
mockDirectory.use {
def f = new File('')
results = helper.getUpperCaseDirectoryNames(f)
}
then:
results == ['FILEONE', 'FILETWO']
}
}
答案 1 :(得分:1)
您不能以这种方式模仿eachDir
,因为此方法不属于File
类 - 它是通过ResourceGroovyMethods
类动态添加的。您必须改为模仿listFiles()
,exists()
和isDirectory()
方法,例如:
File mockParentDir = Mock(File) {
getName() >> 'parent_dir'
listFiles() >> mockSubdirs
exists() >> true
isDirectory() >> true
}
模拟exists()
和isDirectory()
方法是必需的,因为如果你没有指定一个,mock会返回默认值,而对于布尔值,默认值为false
- 在这种情况下你将获得FileNotFoundException
。如果您希望它包含目录,则必须对mockSubdirs
执行相同的操作。
这是一个示例性测试,显示正确的模拟:
import spock.lang.Specification
class MockDirSpec extends Specification {
def "test mocked directories"() {
setup:
def mockSubdirs = []
mockSubdirs << Mock( File ){
getName() >> 'some subdir'
lastModified() >> 2000
exists() >> true
isDirectory() >> true
}
File mockParentDir = Mock(File) {
getName() >> 'parent_dir'
listFiles() >> mockSubdirs
exists() >> true
isDirectory() >> true
}
def cut = new ClassUnderTest()
cut.myDirectory = mockParentDir
when:
def names = cut.names()
then:
names == ['some subdir']
}
static class ClassUnderTest {
File myDirectory
List<String> names() {
def dirNames = []
myDirectory.eachDir {
dirNames << it.name
}
return dirNames
}
}
}
eachDir
- 缺点模拟eachDir
函数有一个主要缺点。根据定义,它是非常具体的功能 - 它仅在子目录上进行迭代。这意味着您的示例中的这部分应用代码:
def dirNames = []
myDirectory.eachDir{
dirNames << it.name
}
根据myDirectory
变量引用的结果产生不同的结果。例如:
myDirectory
指向空目录,dirNames
最终为空myDirectory
指向包含多个文本文件的目录,则dirNames
最终为空myDirectory
指向包含2个子目录和10个文本文件的目录,则dirNames
最终包含2个元素,这些子目录的名称如果我们模拟eachDir
所以它总是接受相同的固定输入文件,如果我们在表示空目录的变量或包含2个子目录和几个文本文件的目录上调用它并不重要 - 结果两种情况总是一样的。
在这种情况下,对我来说更有意义的是模拟输入 - 表示为File
的目录。多亏了这一点,您可以在不创建真实文件的情况下进行模拟:
而且你不必嘲笑eachDir
方法的行为,这是一个巨大的好处。
另一个好处是您不必更改应用代码 - 您仍然可以在里面使用eachDir
功能。当您模拟输入文件而不是模拟eachDir
方法时,您只需提供存储在内存而不是文件系统中的测试数据。想象一下,创建一个所需的文件结构并调查那些File
实例在运行时使用调试器表示的内容 - 您可以使用从真实文件系统获取的值重放来自File
类的所有公共方法返回的内容。这可以给你一个很好的内存和#34;模拟存储在文件系统中时特定目录的外观。并且您将其用作测试中的输入数据,以模拟运行时中发生的情况。这就是我考虑模仿eachDir
有害的原因 - 它会创建一个没有出现在运行时的场景。
鲍勃叔叔还有一篇关于嘲笑的好博文,可以通过以下结论进行总结:
&#34;但是,简而言之,我建议你谨慎地嘲笑。找到一种测试方法 - 设计一种测试方法 - 您的代码,以便它不需要模拟。保留嘲弄建筑上重要的界限;然后对此无情。这些是您系统的重要边界,需要对它们进行管理,不仅仅是为了测试,而是为了一切。&#34;
来源:https://8thlight.com/blog/uncle-bob/2014/05/10/WhenToMock.html
答案 2 :(得分:1)
首先,我要感谢Szymon Stepniak和Jeff Scott Brown各自的答案,这些答案都非常有见地,而且由于这个原因我都投了赞成票。我建议OP接受他最喜欢的一个,不这个,因为在这里我只是将两种方法统一到一个规范中,使用相同的测试类和功能中的可比较变量命名方法。我还简化了子目录的模拟使用,只使用一个模拟对象,该对象在后续调用时通过getName() >>> ['subDir1', 'subDir2']
返回两个不同的文件名。
所以现在我们可以更容易地比较基本上这两种方法:
eachDir
,这是一个特定于Groovy的东西。这里的缺点是,为了实现这种模拟,我们真的需要查看eachDir
的源代码及其辅助方法之一,以便找出究竟需要存根的内容以便制作一切正常。尽管如此,它仍然是直截了当且有效的解决方案IMO。MockFor
混合在一起,让我在第一次遇到它时更难以阅读。但这只是因为我专门使用Spock来测试Java应用程序,即我不是Groovy buff。我对这种方法的喜欢之处在于它可以在不查看eachDir
源代码的情况下工作。package de.scrum_master.stackoverflow
import groovy.mock.interceptor.MockFor
import spock.lang.Specification
class MockDirTest extends Specification {
def "Mock eachDir indirectly via method stubbing"() {
setup:
File subDir = Mock() {
// Stub all methods (in-)directly used by 'eachDir'
getName() >>> ['subDir1', 'subDir2']
lastModified() >> 2000
exists() >> true
isDirectory() >> true
}
File parentDir = Mock() {
// Stub all methods (in-)directly used by 'eachDir'
getName() >> 'parentDir'
listFiles() >> [subDir, subDir]
exists() >> true
isDirectory() >> true
}
def helper = new DirectoryNameHelper()
when:
def result = helper.getUpperCaseDirectoryNames(parentDir)
then:
result == ['SUBDIR1', 'SUBDIR2']
}
def "Mock eachDir directly via MockFor.demand"() {
setup:
File subDir = Mock() {
getName() >>> ['subDir1', 'subDir2' ]
}
def parentDir = new MockFor(File)
parentDir.demand.eachDir { Closure closure ->
closure(subDir)
closure(subDir)
}
def helper = new DirectoryNameHelper()
when:
def result
parentDir.use {
result = helper.getUpperCaseDirectoryNames(new File('parentDir'))
}
then:
result == ['SUBDIR1', 'SUBDIR2']
}
static class DirectoryNameHelper {
List<String> getUpperCaseDirectoryNames(File dir) {
List<String> names = []
dir.eachDir { File f ->
names << f.name.toUpperCase()
}
names
}
}
}