Grails测试插件最核心部分是grails.test.GrailsUnitTestCase类。它是 GroovyTestCase子类,为Grails应用和组件提供测试工具。
这个类为模拟特殊类型提供了若干方法,并且提供了按Groovy的MockFor和StubFor方式模拟的支持。
正常来说你在看之前所示的MyService例子和它对另外一个应用服务的依赖,以及例子中使用到的动态域类方法会有一点痛苦。
你可以在这个例子中使用元类编程和“map asobject”规则,但是很快你会发现使用这些方法会变得很糟糕,那我们要怎么用GrailsUnitTestCase写它的测试呢?import grails.test.GrailsUnitTestCase
class MyServiceTests extends GrailsUnitTestCase {
void testCreateSomething() {
// Mock the domain class.
def testInstances = []
mockDomain(Item, testInstances)
// Mock the "other" service.
String testId = "NH-12347686"
def otherControl = mockFor(OtherService)
otherControl.demand.newIdentifier(1..1) {-> return testId }
// Initialise the service and test the target method.
def testService = new MyService()
testService.otherService = otherControl.createMock()
def retval = testService.createSomething()
// Check that the method returns the identifier returned by the
// mock "other" service and also that a new Item instance has
// been saved.
assertEquals testId, retval
assertEquals 1, testInstances
assertTrue testInstances[0] instanceof Item
}
void testCountItems() {
// Mock the domain class, this time providing a list of test
// Item instances that can be searched.
def testInstances = [ new Item(code: "NH-4273997", name: "Laptop"),
new Item(code: "EC-4395734", name: "Lamp"),
new Item(code: "TF-4927324", name: "Laptop") ]
mockDomain(Item, testInstances)
// Initialise the service and test the target method. def testService = new MyService()
assertEquals 2, testService.countItems("Laptop")
assertEquals 1, testService.countItems("Lamp")
assertEquals 0, testService.countItems("Chair")
}
}上面代码出现了一些新的方法,但是一旦对它们进一步解释,你应该很快懂得要使用这些方法是多么容易。
首先看testCreateSomething()测试方法里调用的mockDomain()方法,这是GrailsUnitTestCase类提供的其中一个方法:def testInstances = []
mockDomain(Item, testInstances)这个方法可以给给定的类添加所有共同域的方法(实例和静态),这样任何使用它的代码段都可以把它当作一个真正全面的domain 类。
举个例子,一旦Item类被模拟了,我们就可以在实例它的时候放心得调用save();那么这时,如果我们调用一个被模拟的domain类的这个方法,要怎么做?
很简单,在testInstances数组列表里添加新的实例,这个数组被当成参数传进mockDomain()方法。
下面我们将重点讲解mockFor方法:def otherControl = mockFor(OtherService)
otherControl.demand.newIdentifier(1..1) {-> return testId }这段代码功能与Groovy中的MockFor类和StubFor类非常接近,你可以用这个方法模拟任何类。
事实上,上述demand语法跟MockFor和StubFor使用的语法一样,所以你在用它时应该不会觉得有差别,当然你要需要频繁注入一个mock实例作为关联,但是你可以简
单得调用上述的mock控制对象的createMock()方法,很容易实现。
对那些熟悉EasyMock用法的人,它们知道这是otherControl强调了mockFor()返回的对象角色,它是一个控制对象而非mock 对象。
testCreateSomething()方法中剩余部分应该很熟悉了,特别是你现在已经知道了save()模拟方法是往testInstances数组里添加实例。
我们能确定newIdentifier()模拟方法被调用,因为它返回的值对createSomething()方法返回结果产生直接的影响。
那假如情况不是这样呢?我们怎么知道它是否被调用?在MockFor类和StubFor类中,use()方法最后会做这个检查,但是testCreateSomething()方法中没有这个方法,然而你可以调用控制对象的verify()方法。
在这个例子中,otherControl对象可以实现。
这个方法会执行检查,在newIdentifier()应该被调用但没有被调用的情况下抛出诊断结果。
最后,这个例子中的testCountItems()向我们展示了mockDomain()方法的另外一个特性:def testInstances = [ new Item(code: "NH-4273997", name: "Laptop"),
new Item(code: "EC-4395734", name: "Lamp"),
new Item(code: "TF-4927324", name: "Laptop") ]
mockDomain(Item, testInstances)通常手工模拟动态遍历器比较烦人,而且你经常不得不为每次执行设置不同的数组;
除了这个之外,假如你决定使用一个不同的遍历器,你就不得不更新测试实例去测试新的方法。
感谢mockDomain()方法为一组域实例的动态遍历器提供了一个轻量级的执行实现,把测试数据简单得作为这个方法的第二个参数,模拟遍历器就会工作了。