如何测试域类约束?
作者: Sergio del Amo
Grails 版本 5.0.1
1 Grails 培训
Grails 培训 - 由创建和积极维护 Grails 框架的人员开发和交付!
2 入门
在本指南中,您将学习如何为域类定义约束,并单独测试这些约束。
2.1 您需要什么
要完成本指南,您需要以下内容
-
一些时间
-
一个不错的文本编辑器或 IDE
-
已安装 JDK 1.8 或更高版本,并正确配置了
JAVA_HOME
2.2 如何完成指南
要开始,请执行以下操作
-
下载并解压缩源代码
或
Grails 指南存储库包含两个文件夹
-
initial
初始项目。通常是一个简单的 Grails 应用程序,它包含一些附加代码,让您可以抢占先机。 -
complete
完成的示例。它是按照指南提出的步骤进行操作并对initial
文件夹应用这些更改的结果。
要完成指南,请转到 initial
文件夹
-
将
cd
变成grails-guides/grails-test-domain-class-constraints/initial
并按照下一部分中的说明进行操作。
如果您 cd 变为 grails-guides/grails-test-domain-class-constraints/complete ,则您可以直接进入完成的示例 |
3 编写应用程序
3.1 域类
创建一个持久实体来存储酒店实体。Grails 中处理持久性的最常见方式是使用Grails 域类
域类满足 Model View Controller (MVC) 模式中的 M,它表示一个持久实体,该实体映射到基础数据库表上。在 Grails 中,域是一个类,位于 grails-app/domain 目录中。
./grailsw create-domain-class Hotel
CONFIGURE SUCCESSFUL
| Created grails-app/domain/demo/Hotel.groovy
| Created src/test/groovy/demo/HotelSpec.groovy
Hotel
域类是我们的数据模型。我们定义不同的属性来存储Hotel
特征。
package demo
@SuppressWarnings('DuplicateNumberLiteral')
class Hotel {
String name
String url
String email
String about
BigDecimal latitude
BigDecimal longitude
static constraints = {
name blank: false, maxSize: 255
url nullable: true, url: true, maxSize: 255
email nullable: true, email: true, unique: true
about nullable: true
// tag::latitudeCustomValidator[]
latitude nullable: true, validator: { val, obj, errors ->
if ( val == null ) {
return true
}
if (val < -90.0) {
errors.rejectValue('latitude', 'range.toosmall')
return false
}
if (val > 90.0) {
errors.rejectValue('latitude', 'range.toobig')
return false
}
true
}
// end::latitudeCustomValidator[]
longitude nullable: true, validator: { val, obj, errors ->
if ( val == null ) {
return true
}
if (val < -180.0) {
errors.rejectValue('longitude', 'range.toosmall')
return false
}
if (val > 180.0) {
errors.rejectValue('longitude', 'range.toobig')
return false
}
true
}
}
// tag::hotelMapping[]
static mapping = {
about type: 'text'
}
// end::hotelMapping[]
}
我们定义多个验证约束
约束为 Grails 提供了一个声明性 DSL,用于定义验证规则、模式生成和 CRUD 生成元数据。
Grails 提供了多个约束,可随时使用
如果需要,你可以定义自己的自定义验证器。 |
3.2 单元测试
我们希望单元测试我们的验证约束。
实施DomainUnitTest
特征。它表示我们正在测试 Grails 工件;即域类。
class HotelSpec extends Specification implements DomainUnitTest<Hotel> {
这是本指南的主要收获
当将字符串列表传递到 validate 方法时,validate 方法只会验证你传递的属性。域类可能具有许多属性。这允许你隔离测试属性验证。
Name 属性约束测试
我们希望将我们的name
属性映射到具有 VARCHAR(255) 的关系数据库中。我们希望此属性为必需属性,最大长度为 255 个字符。
void 'test name cannot be null'() {
when:
domain.name = null
then:
!domain.validate(['name'])
domain.errors['name'].code == 'nullable'
}
void 'test name cannot be blank'() {
when:
domain.name = ''
then:
!domain.validate(['name'])
}
void 'test name can have a maximum of 255 characters'() {
when: 'for a string of 256 characters'
String str = 'a' * 256
domain.name = str
then: 'name validation fails'
!domain.validate(['name'])
domain.errors['name'].code == 'maxSize.exceeded'
when: 'for a string of 256 characters'
str = 'a' * 255
domain.name = str
then: 'name validation passes'
domain.validate(['name'])
}
Url 属性约束测试
我们希望将我们的url
属性映射到具有 VARCHAR(255) 的关系数据库中。我们希望此属性为有效 URL 或者为空或空白值。
@Ignore
void 'test url can have a maximum of 255 characters'() {
when: 'for a string of 256 characters'
String urlprefifx = 'http://'
String urlsufifx = '.com'
String str = 'a' * (256 - (urlprefifx.size() + urlsufifx.size()))
str = urlprefifx + str + urlsufifx
domain.url = str
then: 'url validation fails'
!domain.validate(['url'])
domain.errors['url'].code == 'maxSize.exceeded'
when: 'for a string of 256 characters'
str = "${urlprefifx}${'a' * (255 - (urlprefifx.size() + urlsufifx.size()))}${urlsufifx}"
domain.url = str
then: 'url validation passes'
domain.validate(['url'])
}
@Unroll('Hotel.validate() with url: #value should have returned #expected with errorCode: #expectedErrorCode')
void "test url validation"() {
when:
domain.url = value
then:
expected == domain.validate(['url'])
domain.errors['url']?.code == expectedErrorCode
where:
value | expected | expectedErrorCode
null | true | null
'' | true | null
'http://hilton.com' | true | null
'hilton' | false | 'url.invalid'
}
Email 属性约束测试
我们希望email
为有效电子邮件或者为空或空白值。
@Unroll('Hotel.validate() with email: #value should have returned #expected with errorCode: #expectedErrorCode')
void "test email validation"() {
when:
domain.email = value
then:
expected == domain.validate(['email'])
domain.errors['email']?.code == expectedErrorCode
where:
value | expected | expectedErrorCode
null | true | null
'' | true | null
'[email protected]' | true | null
'hilton' | false | 'email.invalid'
}
电子验证确保它少于 255 个字符 |
Email 唯一约束测试
我们已经向酒店的电子邮箱地址添加了一个unique
约束。
唯一:它将属性约束为在数据库级别唯一。唯一是一个持久调用,将查询数据库。
你可以使用此类测试来测试唯一约束
package demo
import grails.test.hibernate.HibernateSpec
@SuppressWarnings('MethodName')
class HotelEmailUniqueConstraintSpec extends HibernateSpec {
List<Class> getDomainClasses() { [Hotel] }
def "hotel's email unique constraint"() {
when: 'You instantiate a hotel with name and an email address which has been never used before'
def hotel = new Hotel(name: 'Hotel Transilvania', email: '[email protected]')
then: 'hotel is valid instance'
hotel.validate()
and: 'we can save it, and we get back a not null GORM Entity'
hotel.save()
and: 'there is one additional Hotel'
Hotel.count() == old(Hotel.count()) + 1
when: 'instanting a different hotel with the same email address'
def hilton = new Hotel(name: 'Hilton Hotel', email: '[email protected]')
then: 'the hotel instance is not valid'
!hilton.validate(['email'])
and: 'unique error code is populated'
hilton.errors['email']?.code == 'unique'
and: 'trying to save fails too'
!hilton.save()
and: 'no hotel has been added'
Hotel.count() == old(Hotel.count())
}
}
关于属性约束测试
我们希望 about
为一个 null、空白或文本块。我们不希望 about
被限制为 255 个字符。我们使用域类中的 mapping
块来表明此特征。
static mapping = {
about type: 'text'
}
以下是对 about
约束进行验证的测试。
void 'test about can be null'() {
when:
domain.about = null
then:
domain.validate(['about'])
}
void 'test about can be blank'() {
when:
domain.about = ''
then:
domain.validate(['about'])
}
void 'test about can have a more than 255 characters'() {
when: 'for a string of 256 characters'
String str = 'a' * 256
domain.about = str
then: 'about validation passes'
domain.validate(['about'])
}
纬度和经度属性约束测试
我们希望 latitude
为 null 或 -90 至 90 度之间的值。我们希望 longitude
为 null 或 -180 至 180 度之间的值。
你可能想使用
latitude nullable: true, range: -90..90
longitude nullable: true, range: -180..180
但是,使用 范围约束,例如 latitude
为 90.1 的值将是有效值
设置为 Groovy 范围,其中可以包含 IntRange、日期或实现 Comparable 并提供导航 next 和 previous 方法的任何对象形式的数字。
相反,我们正在使用 自定义验证。
latitude nullable: true, validator: { val, obj, errors ->
if ( val == null ) {
return true
}
if (val < -90.0) {
errors.rejectValue('latitude', 'range.toosmall')
return false
}
if (val > 90.0) {
errors.rejectValue('latitude', 'range.toobig')
return false
}
true
}
以下是验证 latitude
和 longitude
约束的测试。
@Unroll('Hotel.validate() with latitude: #value should have returned #expected with errorCode: #expectedErrorCode')
void 'test latitude validation'() {
when:
domain.latitude = value
then:
expected == domain.validate(['latitude'])
domain.errors['latitude']?.code == expectedErrorCode
where:
value | expected | expectedErrorCode
null | true | null
0 | true | null
0.5 | true | null
90 | true | null
90.5 | false | 'range.toobig'
-90 | true | null
-180 | false | 'range.toosmall'
180 | false | 'range.toobig'
}
@Unroll('Hotel.longitude() with latitude: #value should have returned #expected with error code: #expectedErrorCode')
void 'test longitude validation'() {
when:
domain.longitude = value
then:
expected == domain.validate(['longitude'])
domain.errors['longitude']?.code == expectedErrorCode
where:
value | expected | expectedErrorCode
null | true | null
0 | true | null
90 | true | null
90.1 | true | null
-90 | true | null
-180 | true | null
180 | true | null
180.1 | false | 'range.toobig'
-180.1 | false | 'range.toosmall'
}
4 测试应用程序
运行测试
./grailsw
grails> test-app
grails> open test-report
或
./gradlew check
open build/reports/tests/index.html