GMock是groovy环境下的mock解决方法。使用它可以很轻松的完成groovy的单元测试工作。它能够很好的模拟对象,辅助Junit完成单元测试。
下面,就先来看一下最简单的使用的GMock代码,然后在给出详细的说明。
1. @WithGMock
2. public class NewTest extends GroovyTestCase{
3. @Test
4. public void testMock(){
5. def gmc = new GMockController()
6. def mockLoader = gmc.mock()
7. mockLoader.load('key').returns('value')
8. gmc.play {
9. assertEquals "value", mockLoader.load('key')
10. }
11. }
12. }
使用Gmock主要分为以下几个步骤:
1. 测试类需要继承自GMockTestCase,或者是使用Annotation的方式——在类声明处使用@WithGMock
2. 创建GMockController对象,调用其mock()方法,创建一个需要模拟的对象。
3. 通过调用方法,设置期望的输入值和期望的返回值。上述代码中,期望调用的方法是load(),在输入参数为'key'的条件下期望的返回值是'value',使用returns()实现
4. 在GMockController对象上调用play闭包,完成需要的断言。
需要对上述问题做几点说明。使用GMock有两种方法:要么继承自GMockTestCase;要么使用@WithGMock注解。这两种方法在使用上有所区别。如果继承自GMockTestCase,那么在这个父类中已经定义了mock()和play()方法。这样就不需要使用 GMockController对象了。反之,如果使用注解,就示例代码所示,需要自己定义GMockController对象了。
继承自GMockTestCase的代码形式如下:
1. public class NewTest extends GMockTestCase{
2. @Test
3. public void testMock(){
4. def mockLoader = mock()
5. mockLoader.load('key').returns('value')
6. play {
7. assertEquals "value", mockLoader.load('key')
8. }
9. }
10. }
其实示例代码看上去是很怪异的。看一下变量mockLoader,它是什么类型的呢?不知道!它可以指向一个已存在的数据类型,也可以指向一个未知的数据类型。我们所关注的,就是在这个对象上,期望调用一个load方法,在参数为'key'的情况下获得一个'value'的返回值。也许这个方法也是不存在的。
(一)模拟强类型对象
为什么会出现上面说的那种怪异的情况的?因为groovy的弱类型语言。同样,GMock也同样支持像Java一样的强类型。示例如下:
1. public class StrongTypeTest extends GMockTestCase{
2. @Test
3. public void testStrongType(){
4. File mockFile = mock(File, constructor("/a/path/file.txt"))
5. mockFile.getName().returns("file.txt")
6. play {
7. def file = new File("/a/path/file.txt")
8. assertEquals "file.txt", file.getName()
9. }
10. }
11. }
在mock()方法中,接受一个可选参数,也就是需要模拟的类型。同时,如果要模拟的类型需要使用构造函数,则可以通过constructor()来指明构造函数。
(二)模拟异常类型
通常情况下,异常类型在单元测试中很难获得。在这里可以使用raises()来模拟异常类型。
1. def loader = mock()
2. loader.put("throw exception").raises(new RuntimeException("an exception")) // or 'raises(RuntimeException, "an exception")'
3. play {
4. def message = shouldFail(RuntimeException) {
5. loader.put("throw exception")
6. }
7. assertEquals "an exception", message
8. }
(三)模拟静态方法调用
对于静态方法,可以在没有对象的情况下直接使用类名调用,下面就给出示例代码。
1. def mockMath = mock(Math)
2. mockMath.static.random().returns(0.5)
3.
4. play {
5. assertEquals 0.5, Math.random()
6. }
(四)模拟构造函数
1. def mockFile = mock(File, constructor("/a/path/file.txt").raises(RuntimeException))
2. play {
3. shouldFail(RuntimeException) {
4. new File("/a/path/file.txt")
5. }
6. }
之前已经给出了模拟构造函数和模拟异常类型的方法,这里综合使用一下。在构建构造函数时会抛出异常。在play闭包中使用shouldFail来判定。
(五)多次模拟
对于一些特定方法,有可能需要调用多次。如果多次重复设置会产生不必要的麻烦,可以通过相应的方法来设置模拟次数。
1. mockLoader.load(2).returns(3).atLeastOnce()
2. play {
3. assertEquals 3, mockLoader.load(2)
4. assertEquals 3, mockLoader.load(2)
5. }
这里的atLeastOnce()表示该方法至少会被调用一次。还有如下几种方法用来设置调用次数。
(六)顺序调用
如果一些方法的执行顺序有严格的要求,可以使用ordered方法来确定方法的调用顺序。
1. def database = mock()
2. def cache = mock()
3. ordered {
4. database.open()
5. cache.get("select * from cat").returns(null)
6. database.query("select * from cat").returns(["cat1", "cat2"])
7. cache.put("select * from cat", ["cat1", "cat2"])
8. database.close()
9. }
(七)方法的正则表达方式模拟
如果一些需要模拟的方法名有一些规则,可以使用正则表达式匹配的方法批量模拟。
1. def mock = mock()
2. mock./set.*/(1).returns(2)
3. play {
4. assertEquals 2, mock.setSomething(1)
5. }
(八)默认模拟
Gmock默认实现了equals()、hashCode()和toString()的模拟。
* never() 从来不被调用
* once() 默认情况,只被调用一次
* atLeastOnce() 至少被调用一次
* atMostOnce() 最多一次(0或1)
* stub() the expectation can be called anytime
* times(n) 被调用n次。
* times(m..n) 被调用m-n次。
* atLeast(n) 被调用至少n次
* atMost(n) 被调用至多n次
特性
方法调用:mockLoader.load("fruit").returns("apple")
抛出异常:mockLoader.load("unknown").raises(new RuntimeException()) 或 mockLoader.load("unknown").raises(RuntimeException)
stub:mockLoader.load("fruit").returns("apple").stub()
静态方法:mockMath.static.random().returns(0.5)
属性:mockLoader.name.returns("loader")
构造函数:def mockFile = mock(File, constructor('/a/path/file.txt'))
预期调用次数:mockLoader.load("fruit").returns("apple").atLeastOnce()
自定义匹配器:mockLoader.load(match { it.startsWith("fru") })
支持 Hamcrest匹配器:mockLoader.put("test", is(not(lessThan(5))))
如果你的测试类不能继承GMockTestCase,那么请使用GMockController类
下面,就先来看一下最简单的使用的GMock代码,然后在给出详细的说明。
1. @WithGMock
2. public class NewTest extends GroovyTestCase{
3. @Test
4. public void testMock(){
5. def gmc = new GMockController()
6. def mockLoader = gmc.mock()
7. mockLoader.load('key').returns('value')
8. gmc.play {
9. assertEquals "value", mockLoader.load('key')
10. }
11. }
12. }
使用Gmock主要分为以下几个步骤:
1. 测试类需要继承自GMockTestCase,或者是使用Annotation的方式——在类声明处使用@WithGMock
2. 创建GMockController对象,调用其mock()方法,创建一个需要模拟的对象。
3. 通过调用方法,设置期望的输入值和期望的返回值。上述代码中,期望调用的方法是load(),在输入参数为'key'的条件下期望的返回值是'value',使用returns()实现
4. 在GMockController对象上调用play闭包,完成需要的断言。
需要对上述问题做几点说明。使用GMock有两种方法:要么继承自GMockTestCase;要么使用@WithGMock注解。这两种方法在使用上有所区别。如果继承自GMockTestCase,那么在这个父类中已经定义了mock()和play()方法。这样就不需要使用 GMockController对象了。反之,如果使用注解,就示例代码所示,需要自己定义GMockController对象了。
继承自GMockTestCase的代码形式如下:
1. public class NewTest extends GMockTestCase{
2. @Test
3. public void testMock(){
4. def mockLoader = mock()
5. mockLoader.load('key').returns('value')
6. play {
7. assertEquals "value", mockLoader.load('key')
8. }
9. }
10. }
其实示例代码看上去是很怪异的。看一下变量mockLoader,它是什么类型的呢?不知道!它可以指向一个已存在的数据类型,也可以指向一个未知的数据类型。我们所关注的,就是在这个对象上,期望调用一个load方法,在参数为'key'的情况下获得一个'value'的返回值。也许这个方法也是不存在的。
(一)模拟强类型对象
为什么会出现上面说的那种怪异的情况的?因为groovy的弱类型语言。同样,GMock也同样支持像Java一样的强类型。示例如下:
1. public class StrongTypeTest extends GMockTestCase{
2. @Test
3. public void testStrongType(){
4. File mockFile = mock(File, constructor("/a/path/file.txt"))
5. mockFile.getName().returns("file.txt")
6. play {
7. def file = new File("/a/path/file.txt")
8. assertEquals "file.txt", file.getName()
9. }
10. }
11. }
在mock()方法中,接受一个可选参数,也就是需要模拟的类型。同时,如果要模拟的类型需要使用构造函数,则可以通过constructor()来指明构造函数。
(二)模拟异常类型
通常情况下,异常类型在单元测试中很难获得。在这里可以使用raises()来模拟异常类型。
1. def loader = mock()
2. loader.put("throw exception").raises(new RuntimeException("an exception")) // or 'raises(RuntimeException, "an exception")'
3. play {
4. def message = shouldFail(RuntimeException) {
5. loader.put("throw exception")
6. }
7. assertEquals "an exception", message
8. }
(三)模拟静态方法调用
对于静态方法,可以在没有对象的情况下直接使用类名调用,下面就给出示例代码。
1. def mockMath = mock(Math)
2. mockMath.static.random().returns(0.5)
3.
4. play {
5. assertEquals 0.5, Math.random()
6. }
(四)模拟构造函数
1. def mockFile = mock(File, constructor("/a/path/file.txt").raises(RuntimeException))
2. play {
3. shouldFail(RuntimeException) {
4. new File("/a/path/file.txt")
5. }
6. }
之前已经给出了模拟构造函数和模拟异常类型的方法,这里综合使用一下。在构建构造函数时会抛出异常。在play闭包中使用shouldFail来判定。
(五)多次模拟
对于一些特定方法,有可能需要调用多次。如果多次重复设置会产生不必要的麻烦,可以通过相应的方法来设置模拟次数。
1. mockLoader.load(2).returns(3).atLeastOnce()
2. play {
3. assertEquals 3, mockLoader.load(2)
4. assertEquals 3, mockLoader.load(2)
5. }
这里的atLeastOnce()表示该方法至少会被调用一次。还有如下几种方法用来设置调用次数。
(六)顺序调用
如果一些方法的执行顺序有严格的要求,可以使用ordered方法来确定方法的调用顺序。
1. def database = mock()
2. def cache = mock()
3. ordered {
4. database.open()
5. cache.get("select * from cat").returns(null)
6. database.query("select * from cat").returns(["cat1", "cat2"])
7. cache.put("select * from cat", ["cat1", "cat2"])
8. database.close()
9. }
(七)方法的正则表达方式模拟
如果一些需要模拟的方法名有一些规则,可以使用正则表达式匹配的方法批量模拟。
1. def mock = mock()
2. mock./set.*/(1).returns(2)
3. play {
4. assertEquals 2, mock.setSomething(1)
5. }
(八)默认模拟
Gmock默认实现了equals()、hashCode()和toString()的模拟。
* never() 从来不被调用
* once() 默认情况,只被调用一次
* atLeastOnce() 至少被调用一次
* atMostOnce() 最多一次(0或1)
* stub() the expectation can be called anytime
* times(n) 被调用n次。
* times(m..n) 被调用m-n次。
* atLeast(n) 被调用至少n次
* atMost(n) 被调用至多n次
特性
方法调用:mockLoader.load("fruit").returns("apple")
抛出异常:mockLoader.load("unknown").raises(new RuntimeException()) 或 mockLoader.load("unknown").raises(RuntimeException)
stub:mockLoader.load("fruit").returns("apple").stub()
静态方法:mockMath.static.random().returns(0.5)
属性:mockLoader.name.returns("loader")
构造函数:def mockFile = mock(File, constructor('/a/path/file.txt'))
预期调用次数:mockLoader.load("fruit").returns("apple").atLeastOnce()
自定义匹配器:mockLoader.load(match { it.startsWith("fru") })
支持 Hamcrest匹配器:mockLoader.put("test", is(not(lessThan(5))))
如果你的测试类不能继承GMockTestCase,那么请使用GMockController类