├── .github ├── actions │ ├── pages-deploy │ │ ├── Dockerfile │ │ ├── LICENSE │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── post-release │ │ ├── Dockerfile │ │ ├── action.yml │ │ ├── entrypoint.sh │ │ └── increment_version.sh │ └── pre-release │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh └── workflows │ ├── gradle.yml │ └── release.yml ├── .gitignore ├── .sdkmanrc ├── LICENSE ├── README.md ├── RELEASE.md ├── build.gradle ├── docs ├── build.gradle └── src │ └── docs │ ├── examples.adoc │ ├── features.adoc │ ├── index.adoc │ ├── index.tmpl │ ├── installation.adoc │ ├── integrationtesting.adoc │ ├── introduction.adoc │ ├── methodology.adoc │ ├── testdataconfig.adoc │ ├── unittesting.adoc │ ├── upgrading.adoc │ └── usage.adoc ├── examples ├── alternativeConfig │ ├── build.gradle │ ├── grails-app │ │ ├── conf │ │ │ ├── application.yml │ │ │ ├── logback-spring.xml │ │ │ └── spring │ │ │ │ └── resources.groovy │ │ ├── domain │ │ │ └── alternativeconfig │ │ │ │ └── Simple.groovy │ │ ├── i18n │ │ │ └── messages.properties │ │ └── init │ │ │ ├── BootStrap.groovy │ │ │ └── alternativeconfig │ │ │ └── Application.groovy │ └── src │ │ └── test │ │ ├── groovy │ │ └── alternativeconfig │ │ │ └── SimpleSpec.groovy │ │ └── resources │ │ └── AlternativeTestDataConfig.groovy └── bookStore │ ├── build.gradle │ ├── grails-app │ ├── conf │ │ ├── application.yml │ │ ├── logback-spring.xml │ │ └── spring │ │ │ └── resources.groovy │ ├── domain │ │ ├── abstractclass │ │ │ ├── AbstractClass.groovy │ │ │ ├── AbstractSubClass.groovy │ │ │ ├── AnotherConcreteSubClass.groovy │ │ │ ├── ConcreteSubClass.groovy │ │ │ └── RelatedToAbstract.groovy │ │ ├── bookstore │ │ │ ├── Address.groovy │ │ │ ├── Author.groovy │ │ │ ├── Book.groovy │ │ │ ├── BookInfo.groovy │ │ │ ├── Customer.groovy │ │ │ ├── EstablishedAuthor.groovy │ │ │ ├── Invoice.groovy │ │ │ └── Payment.groovy │ │ ├── config │ │ │ ├── Article.groovy │ │ │ └── Hotel.groovy │ │ ├── embedded │ │ │ └── Embedding.groovy │ │ ├── enumtest │ │ │ ├── Car.groovy │ │ │ └── Door.groovy │ │ ├── hibernate4 │ │ │ ├── Gallery.groovy │ │ │ ├── Painter.groovy │ │ │ └── Painting.groovy │ │ ├── human │ │ │ ├── Arm.groovy │ │ │ ├── Face.groovy │ │ │ ├── Hand.groovy │ │ │ └── Nose.groovy │ │ ├── list │ │ │ ├── Child.groovy │ │ │ ├── Elf.groovy │ │ │ └── Santa.groovy │ │ ├── magazine │ │ │ ├── Advertisment.groovy │ │ │ ├── Issue.groovy │ │ │ └── Page.groovy │ │ ├── mapping │ │ │ └── AutoTimestampDomain.groovy │ │ ├── standalone │ │ │ ├── AssignedKey.groovy │ │ │ ├── ChildWithAssignedKey.groovy │ │ │ ├── ParentWithAssignedKey.groovy │ │ │ └── Standalone.groovy │ │ ├── subclassing │ │ │ ├── RelatedClass.groovy │ │ │ ├── SubClass.groovy │ │ │ └── SuperClass.groovy │ │ └── triangle │ │ │ ├── Director.groovy │ │ │ ├── Manager.groovy │ │ │ └── Worker.groovy │ └── init │ │ ├── BootStrap.groovy │ │ └── bookstore │ │ └── Application.groovy │ └── src │ ├── integration-test │ └── groovy │ │ ├── base │ │ ├── AbstractTests.groovy │ │ ├── AssignedKeyTests.groovy │ │ ├── BuildLazyTests.groovy │ │ ├── ConfigWithoutResetTests.groovy │ │ ├── DoWithTestDataConfigTests.groovy │ │ ├── EnumTests.groovy │ │ ├── LocalScopedConfigTests.groovy │ │ ├── MinSizeTests.groovy │ │ ├── RelationTests.groovy │ │ ├── StandaloneTests.groovy │ │ ├── StandaloneTestsWithBuildTrait.groovy │ │ ├── SubclassTests.groovy │ │ └── TestDataConfigTests.groovy │ │ ├── embedded │ │ └── EmbeddingTests.groovy │ │ ├── list │ │ ├── EstablishedAuthorTests.groovy │ │ └── SantaTests.groovy │ │ └── triangle │ │ └── TriangleRelationshipTests.groovy │ ├── main │ └── groovy │ │ ├── embedded │ │ ├── Embedded.groovy │ │ └── LatLon.groovy │ │ └── enums │ │ └── BookType.groovy │ └── test │ ├── groovy │ ├── base │ │ ├── AbstractDefaultUnitTests.groovy │ │ ├── AbstractRelatedUnitTests.groovy │ │ ├── BuildLazyUnitTests.groovy │ │ ├── ConfigWithoutResetUnitTests.groovy │ │ ├── EnumUnitTests.groovy │ │ ├── LocalScopedConfigUnitTests.groovy │ │ ├── MatchesUnitTests.groovy │ │ ├── MinSizeUnitTests.groovy │ │ ├── RelationUnitTests.groovy │ │ ├── RelationUnitTestsUsingBuild.groovy │ │ ├── StandaloneUnitTests.groovy │ │ ├── SubclassUnitTests.groovy │ │ ├── TestDataConfigUnitTests.groovy │ │ ├── ToManyBasicTypeSpec.groovy │ │ └── ValidateableBuilderSpec.groovy │ ├── embedded │ │ └── EmbeddingUnitTests.groovy │ ├── hibernate4 │ │ └── PaintingBuildSpec.groovy │ ├── list │ │ ├── EstablishedAuthorUnitTests.groovy │ │ └── SantaUnitTests.groovy │ └── triangle │ │ └── TriangleRelationshipUnitTests.groovy │ └── resources │ └── TestDataConfig.groovy ├── gradle.properties ├── gradle ├── docs.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── plugin ├── build.gradle ├── grails-app │ ├── conf │ │ ├── application.yml │ │ └── logback-spring.xml │ └── init │ │ └── grails │ │ └── plugins │ │ └── buildtestdata │ │ └── Application.groovy └── src │ ├── main │ ├── groovy │ │ └── grails │ │ │ └── buildtestdata │ │ │ ├── BuildDataTest.groovy │ │ │ ├── BuildDomainTest.groovy │ │ │ ├── BuildHibernateTest.groovy │ │ │ ├── BuildTestDataGrailsPlugin.groovy │ │ │ ├── TestData.groovy │ │ │ ├── TestDataBuilder.groovy │ │ │ ├── TestDataConfigurationHolder.groovy │ │ │ ├── UnitTestDataBuilder.groovy │ │ │ ├── builders │ │ │ ├── DataBuilder.groovy │ │ │ ├── DataBuilderContext.groovy │ │ │ ├── DataBuilderFactory.groovy │ │ │ ├── PersistentEntityDataBuilder.groovy │ │ │ ├── PogoDataBuilder.groovy │ │ │ └── ValidateableDataBuilder.groovy │ │ │ ├── handler │ │ │ ├── AbstractHandler.groovy │ │ │ ├── AssociationMinSizeHandler.groovy │ │ │ ├── BlankConstraintHandler.groovy │ │ │ ├── ConstraintHandler.groovy │ │ │ ├── ConstraintHandlerException.groovy │ │ │ ├── CreditCardConstraintHandler.groovy │ │ │ ├── EmailConstraintHandler.groovy │ │ │ ├── ExampleConstraintHandler.groovy │ │ │ ├── InListConstraintHandler.groovy │ │ │ ├── MatchesConstraintHandler.groovy │ │ │ ├── MaxConstraintHandler.groovy │ │ │ ├── MaxSizeConstraintHandler.groovy │ │ │ ├── MinConstraintHandler.groovy │ │ │ ├── MinSizeConstraintHandler.groovy │ │ │ ├── NullableConstraintHandler.groovy │ │ │ ├── PersistentEntityNullableConstraintHandler.groovy │ │ │ ├── RangeConstraintHandler.groovy │ │ │ ├── SizeConstraintHandler.groovy │ │ │ ├── UniqueConstraintHandler.groovy │ │ │ ├── UrlConstraintHandler.groovy │ │ │ └── ValidatorConstraintHandler.groovy │ │ │ ├── mixin │ │ │ └── Build.groovy │ │ │ ├── propsresolver │ │ │ ├── ClosurePropsResolver.groovy │ │ │ ├── InitialPropsResolver.groovy │ │ │ └── MapPropsResolver.groovy │ │ │ ├── testing │ │ │ └── DependencyDataTest.groovy │ │ │ └── utils │ │ │ ├── Basics.groovy │ │ │ ├── DomainUtil.groovy │ │ │ ├── IsoDateUtil.groovy │ │ │ └── MetaHelper.groovy │ ├── java │ │ └── nl │ │ │ └── flotsam │ │ │ └── xeger │ │ │ └── Xeger.java │ └── resources │ │ └── idea │ │ └── buildTestData.gdsl │ └── test │ └── groovy │ ├── basetests │ ├── BelongsToSpec.groovy │ ├── BooleanTests.groovy │ ├── ByteTests.groovy │ ├── DateTests.groovy │ ├── DomainTestBase.groovy │ ├── DomainTestDefaultTests.groovy │ ├── ExampleDataSpec.groovy │ ├── HasManyMinSizeSpec.groovy │ ├── HasManySpec.groovy │ ├── IncludesSpec.groovy │ ├── JSR310Tests.groovy │ ├── NumberTests.groovy │ └── StringTests.groovy │ ├── grails │ └── buildtestdata │ │ └── BuildDomainTestSpec.groovy │ ├── hibernate │ ├── domains │ │ ├── Bar.groovy │ │ └── Foo.groovy │ └── specs │ │ ├── HibDomainClassSpec.groovy │ │ └── HibernateSpecSpec.groovy │ └── testdataconfig │ └── TestDataConfigurationHolderTests.groovy └── settings.gradle /.github/actions/pages-deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM cgr.dev/chainguard/wolfi-base:latest 2 | 3 | LABEL "com.github.actions.name"="Deploy to GitHub Pages" 4 | LABEL "com.github.actions.description"="This action will handle the building and deploying process of your project to GitHub Pages." 5 | LABEL "com.github.actions.icon"="git-commit" 6 | LABEL "com.github.actions.color"="orange" 7 | 8 | LABEL "repository"="http://github.com/JamesIves/gh-pages-github-action" 9 | LABEL "homepage"="http://github.com/JamesIves/gh-pages-gh-action" 10 | LABEL "maintainer"="James Ives " 11 | 12 | # Install git and bash 13 | RUN apk update \ 14 | && apk --no-cache add git bash 15 | 16 | ADD entrypoint.sh /entrypoint.sh 17 | ENTRYPOINT ["/entrypoint.sh"] 18 | -------------------------------------------------------------------------------- /.github/actions/pages-deploy/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 James Ives 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/actions/pages-deploy/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Host on GitHub Pages' 2 | description: 'This action will handle the building and deploying process of your project to GitHub Pages.' 3 | author: 'James Ives ' 4 | runs: 5 | using: 'docker' 6 | image: 'Dockerfile' 7 | branding: 8 | icon: 'git-commit' 9 | color: 'orange' 10 | -------------------------------------------------------------------------------- /.github/actions/post-release/Dockerfile: -------------------------------------------------------------------------------- 1 | # Container image that runs your code 2 | FROM alpine:3 3 | 4 | RUN apk add --no-cache curl jq git bash 5 | 6 | # Copies your code file from your action repository to the filesystem path `/` of the container 7 | COPY *.sh / 8 | 9 | # Code file to execute when the docker container starts up (`entrypoint.sh`) 10 | ENTRYPOINT ["/entrypoint.sh"] 11 | -------------------------------------------------------------------------------- /.github/actions/post-release/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Post-release action' 2 | description: 'Performs some actions after doing a release' 3 | inputs: 4 | token: 5 | description: 'GitHub token to authenticate the requests' 6 | required: true 7 | default: ${{ github.token }} 8 | runs: 9 | using: 'docker' 10 | image: 'Dockerfile' 11 | args: 12 | - ${{ inputs.token }} -------------------------------------------------------------------------------- /.github/actions/post-release/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # $1 == GH_TOKEN 3 | 4 | if [ -z "$SNAPSHOT_SUFFIX" ]; then 5 | SNAPSHOT_SUFFIX="-SNAPSHOT" 6 | fi 7 | 8 | if [ -z "$GIT_USER_EMAIL" ]; then 9 | GIT_USER_EMAIL="${GITHUB_ACTOR}@users.noreply.github.com" 10 | fi 11 | 12 | if [ -z "$GIT_USER_NAME" ]; then 13 | GIT_USER_NAME="grails-build" 14 | fi 15 | 16 | echo -n "Determining release version: " 17 | if [ -z "$RELEASE_VERSION" ]; then 18 | release_version=${GITHUB_REF:11} 19 | else 20 | release_version=${RELEASE_VERSION} 21 | fi 22 | echo $release_version 23 | 24 | echo -n "Determining next version: " 25 | next_version=`/increment_version.sh -p $release_version` 26 | echo $next_version 27 | echo "next_version=${next_version}" >> $GITHUB_OUTPUT 28 | 29 | echo "Configuring git" 30 | git config --global --add safe.directory /github/workspace 31 | git config --global user.email "$GIT_USER_EMAIL" 32 | git config --global user.name "$GIT_USER_NAME" 33 | git fetch 34 | 35 | echo -n "Determining target branch: " 36 | if [ -z "$TARGET_BRANCH" ]; then 37 | target_branch=`cat $GITHUB_EVENT_PATH | jq '.release.target_commitish' | sed -e 's/^"\(.*\)"$/\1/g'` 38 | else 39 | target_branch=${TARGET_BRANCH} 40 | fi 41 | echo $target_branch 42 | git checkout $target_branch 43 | 44 | echo -n "Retrieving current milestone number: " 45 | milestone_number=`curl -s https://api.github.com/repos/$GITHUB_REPOSITORY/milestones | jq -c ".[] | select (.title == \"$release_version\") | .number" | sed -e 's/"//g'` 46 | echo $milestone_number 47 | 48 | echo "Closing current milestone" 49 | curl -s --request PATCH -H "Authorization: Bearer $1" -H "Content-Type: application/json" https://api.github.com/repos/$GITHUB_REPOSITORY/milestones/$milestone_number --data '{"state":"closed"}' 50 | 51 | echo "Creating new milestone" 52 | curl -s --request POST -H "Authorization: Bearer $1" -H "Content-Type: application/json" "https://api.github.com/repos/$GITHUB_REPOSITORY/milestones" --data "{\"title\": \"$next_version\"}" 53 | 54 | echo "Setting new snapshot version" 55 | sed -i "s/^projectVersion.*$/projectVersion\=${next_version}$SNAPSHOT_SUFFIX/" gradle.properties 56 | cat gradle.properties 57 | 58 | echo "Committing and pushing" 59 | git add gradle.properties 60 | git commit -m "chore: Bump version to ${next_version}$SNAPSHOT_SUFFIX" 61 | git push origin $target_branch 62 | 63 | # Clean up .git artifacts we've created as root (so non-docker actions that follow can use git without re-cloning) 64 | echo "Cleaning up artifacts with excessive permissions" 65 | rm -f .git/COMMIT_EDITMSG 66 | 67 | echo "Setting release version back so that Maven Central sync can work" 68 | sed -i "s/^projectVersion.*$/projectVersion\=${release_version}/" gradle.properties 69 | cat gradle.properties 70 | -------------------------------------------------------------------------------- /.github/actions/post-release/increment_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Increment a version string using Semantic Versioning (SemVer) terminology. 4 | # Source: https://github.com/fmahnke/shell-semver 5 | 6 | # Parse command line options. 7 | 8 | while getopts ":Mmp" Option 9 | do 10 | case $Option in 11 | M ) major=true;; 12 | m ) minor=true;; 13 | p ) patch=true;; 14 | esac 15 | done 16 | 17 | shift $(($OPTIND - 1)) 18 | 19 | version=$1 20 | 21 | # Build array from version string. 22 | 23 | a=( ${version//./ } ) 24 | 25 | # Increment version numbers as requested. 26 | 27 | if [ ! -z $major ] 28 | then 29 | ((a[0]++)) 30 | a[1]=0 31 | a[2]=0 32 | fi 33 | 34 | if [ ! -z $minor ] 35 | then 36 | ((a[1]++)) 37 | a[2]=0 38 | fi 39 | 40 | if [ ! -z $patch ] && ! [[ "${a[2]}" =~ M.*|RC.* ]] 41 | then 42 | ((a[2]++)) 43 | else 44 | a[2]=0 45 | fi 46 | 47 | echo "${a[0]}.${a[1]}.${a[2]}" 48 | 49 | -------------------------------------------------------------------------------- /.github/actions/pre-release/Dockerfile: -------------------------------------------------------------------------------- 1 | # Container image that runs your code 2 | FROM alpine:3 3 | 4 | RUN apk add --no-cache curl jq git bash 5 | 6 | # Copies your code file from your action repository to the filesystem path `/` of the container 7 | COPY entrypoint.sh /entrypoint.sh 8 | 9 | # Code file to execute when the docker container starts up (`entrypoint.sh`) 10 | ENTRYPOINT ["/entrypoint.sh"] -------------------------------------------------------------------------------- /.github/actions/pre-release/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Pre-release action' 2 | description: 'Performs some actions before doing a release' 3 | inputs: 4 | token: 5 | description: 'GitHub token to authenticate the requests' 6 | required: true 7 | default: ${{ github.token }} 8 | runs: 9 | using: 'docker' 10 | image: 'Dockerfile' 11 | args: 12 | - ${{ inputs.token }} -------------------------------------------------------------------------------- /.github/actions/pre-release/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # $1 == GH_TOKEN 3 | 4 | if [ -z "$RELEASE_VERSION" ]; then 5 | echo -n "Determining release version: " 6 | release_version=${GITHUB_REF:11} 7 | else 8 | release_version=${RELEASE_VERSION} 9 | fi 10 | echo $release_version 11 | 12 | if [ -z "$GIT_USER_NAME" ]; then 13 | echo "GIT_USER_NAME not defined." 14 | exit 1 15 | fi 16 | if [ -z "$GIT_USER_EMAIL" ]; then 17 | echo "GIT_USER_EMAIL not defined." 18 | exit 1 19 | fi 20 | 21 | echo "Configuring git" 22 | git config --global --add safe.directory /github/workspace 23 | git config --global user.email "$GIT_USER_EMAIL" 24 | git config --global user.name "$GIT_USER_NAME" 25 | git fetch 26 | 27 | if [ -z "$TARGET_BRANCH" ]; then 28 | echo -n "Determining target branch: " 29 | target_branch=`cat $GITHUB_EVENT_PATH | jq '.release.target_commitish' | sed -e 's/^"\(.*\)"$/\1/g'` 30 | echo $target_branch 31 | else 32 | target_branch=$TARGET_BRANCH 33 | fi 34 | 35 | git checkout $target_branch 36 | 37 | echo "Setting release version in gradle.properties" 38 | sed -i "s/^projectVersion.*$/projectVersion\=${release_version}/" gradle.properties 39 | cat gradle.properties 40 | 41 | echo "Pushing release version and recreating v${release_version} tag" 42 | git add gradle.properties 43 | git commit -m "[skip ci] Release v${release_version}" 44 | git push origin $target_branch 45 | git tag -fa v${release_version} -m "Release v${release_version}" 46 | git push origin $target_branch 47 | # force push the updated tag 48 | git push origin v${release_version} --force 49 | 50 | echo "Closing again the release after updating the tag" 51 | if [ -z "$RELEASE_URL" ]; then 52 | echo -n "Determining Release RELEASE_URL: " 53 | release_url=`cat $GITHUB_EVENT_PATH | jq '.release.url' | sed -e 's/^"\(.*\)"$/\1/g'` 54 | else 55 | release_url=$RELEASE_URL 56 | fi 57 | echo $release_url 58 | curl -s --request PATCH -H "Authorization: Bearer $1" -H "Content-Type: application/json" $release_url --data "{\"draft\": false}" 59 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: "Java CI" 2 | on: 3 | push: 4 | branches: 5 | - '[5-9].[0-9].x' 6 | pull_request: 7 | types: [ opened, reopened, synchronize ] 8 | workflow_dispatch: 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | java: ['17', '21'] 15 | steps: 16 | - name: "📥 Checkout the repository" 17 | uses: actions/checkout@v4 18 | - name: "☕️ Setup JDK" 19 | uses: actions/setup-java@v4 20 | with: 21 | distribution: 'liberica' 22 | java-version: ${{ matrix.java }} 23 | - name: "🐘 Setup Gradle" 24 | uses: gradle/actions/setup-gradle@v4 25 | - name: "🔨 Run Base Tests" 26 | run: ./gradlew check --continue 27 | - name: "📤️ Upload Base Tests Results - alternativeConfig" 28 | if: ${{ always() }} 29 | uses: actions/upload-artifact@v4 30 | with: 31 | name: testreport-alternativeConfig-${{ matrix.java }} 32 | path: examples/alternativeConfig/build/reports/tests 33 | - name: "📤 Upload Base Tests Results - bookStore" 34 | if: ${{ always() }} 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: testreport-bookStore-${{ matrix.java }} 38 | path: examples/bookStore/build/reports/tests 39 | publish: 40 | if: github.event_name == 'push' 41 | needs: build 42 | runs-on: ubuntu-latest 43 | permissions: 44 | contents: write # publishing docs 45 | packages: write 46 | pages: write 47 | steps: 48 | - name: "📥 Checkout the repository" 49 | uses: actions/checkout@v4 50 | - name: "☕️ Setup JDK" 51 | uses: actions/setup-java@v4 52 | with: 53 | distribution: 'liberica' 54 | java-version: '17' 55 | - name: "🐘 Setup Gradle" 56 | uses: gradle/actions/setup-gradle@v4 57 | - name: Read project version 58 | id: version 59 | run: | 60 | # Extract the version from the property file. 61 | version=$(grep '^projectVersion=' gradle.properties | cut -d= -f2) 62 | echo "version=$version" >> $GITHUB_OUTPUT 63 | shell: bash 64 | - name: "📤 Publish to Snapshot" 65 | if: ${{ success() && endsWith( steps.version.outputs.version, '-SNAPSHOT' ) }} 66 | env: 67 | MAVEN_PUBLISH_USERNAME: ${{ secrets.GITHUB_ACTOR }} 68 | MAVEN_PUBLISH_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 69 | MAVEN_PUBLISH_URL: ${{ secrets.MAVEN_PUBLISH_SNAPSHOT_URL }} 70 | GRAILS_PUBLISH_RELEASE: "false" 71 | working-directory: ./plugin 72 | run: ../gradlew publish 73 | - name: "📜 Generate User Guide Documentation" 74 | if: success() 75 | run: ./gradlew docs 76 | - name: "🚀 Publish to Github Pages" 77 | if: ${{ success() && endsWith( steps.version.outputs.version, '-SNAPSHOT' ) }} 78 | uses: ./.github/actions/pages-deploy 79 | env: 80 | SKIP_SNAPSHOT: ${{ contains(needs.publish.outputs.release_version, 'M') }} 81 | TARGET_REPOSITORY: ${{ github.repository }} 82 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | BRANCH: gh-pages 84 | FOLDER: build/docs 85 | DOC_FOLDER: gh-pages -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | *.iws 3 | *~ 4 | *.iml 5 | *.ipr 6 | **/.idea 7 | *.un~ 8 | */stacktrace.log 9 | */*.tmproj 10 | */*.launch 11 | *.txt 12 | target 13 | */.DS_Store 14 | */reports/ 15 | */*.orig 16 | */*.zip 17 | \.orig\..*$ 18 | \.orig$ 19 | \.chg\..*$ 20 | \.rej$ 21 | \.conflict\~$ 22 | *.png 23 | *.gif 24 | *.jpg 25 | *.js 26 | *.tld 27 | *.ico 28 | *.css 29 | UrlMappings.groovy 30 | sitemesh.xml 31 | applicationContext.xml 32 | plugin.xml 33 | 34 | .DS_Store 35 | 36 | bookStore/web-app/plugins/* 37 | testOptionalJars/web-app/plugins/* 38 | .hg* 39 | 40 | build-test-data/build/* 41 | build-test-data/.gradle/* 42 | .gradle 43 | build/ 44 | -------------------------------------------------------------------------------- /.sdkmanrc: -------------------------------------------------------------------------------- 1 | # Enable auto-env through the sdkman_auto_env config - https://sdkman.io/usage#env 2 | java=17.0.12-librca 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Build Test Data 2 | === 3 | [![Java CI](https://github.com/longwa/build-test-data/actions/workflows/gradle.yml/badge.svg?event=push)](https://github.com/longwa/build-test-data/actions/workflows/gradle.yml) 4 | 5 | ## Build Test Data Grails Plugin 6 | ### Grails 7.x or later 7 | `testImplementation 'io.github.longwa:build-test-data:6.0.0-M1'` 8 | 9 | http://longwa.github.io/build-test-data/index 10 | 11 | ### Grails 5.x through 6.x 12 | `testImplementation 'io.github.longwa:build-test-data:5.0.0'` 13 | 14 | _Note: The Group ID has changed but the internal package name remains grails.buildtestdata.*_ 15 | 16 | http://longwa.github.io/build-test-data/index 17 | 18 | ### Grails 3.3 through 4.0.x 19 | 20 | `testCompile 'org.grails.plugins:build-test-data:4.0.0'` 21 | 22 | http://longwa.github.io/build-test-data/index 23 | 24 | ### Grails 2.4 - 3.2 25 | 26 | `testCompile 'org.grails.plugins:build-test-data:3.3.1'` 27 | 28 | https://github.com/longwa/build-test-data/wiki 29 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | Release Management 2 | === 3 | 4 | Currently this project publishes snapshots to the GitHub packages repository and releases to Maven Central. Change the `snapshotPublishType` on `grailsPublish` extension to switch to a nexus publishing. 5 | 6 | Required `Action` Secrets 7 | --- 8 | Under the GitHub project's `Settings` -> `Secrets and variables` -> `Actions`, the following `Repository secrets` should exist: 9 | * For snapshots: 10 | * `MAVEN_PUBLISH_SNAPSHOT_URL` - the GitHub package location. See the GitHub [help documentation](https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-java-packages-with-gradle#publishing-packages-to-github-packages) for this value. 11 | * For releases: 12 | * `NEXUS_PUBLISH_USERNAME` - The sonatype username that can access the `NEXUS_PUBLISH_URL`. 13 | * `NEXUS_PUBLISH_PASSWORD` - The sonatype password that can access the `NEXUS_PUBLISH_URL`. 14 | * `NEXUS_PUBLISH_URL` - The release url for Maven Central, typically `https://s01.oss.sonatype.org/service/local/` 15 | * `NEXUS_PUBLISH_STAGING_PROFILE_ID` - The Nexus Staging Profile ID 16 | * `SIGNING_KEY` - The public key ID. 17 | * `SIGNING_PASSPHRASE` - The passphrase used while generating GnuPG key. 18 | * `SECRING_FILE` - The `secring.gpg` file contents for publishing to Maven Central. 19 | 20 | See this [Grails Blog Post](https://grails.org/blog/2021-04-07-publish-grails-plugin-to-maven-central.html) for help setting up this information. 21 | 22 | Version Numbering 23 | --- 24 | Releases are tracked based on a major project version with new branches created on each new major release. 25 | 26 | Pull Requests 27 | --- 28 | Pull requests will only run tests with no publishing of documentation or builds. 29 | 30 | Push 31 | --- 32 | Pushes to a major branch (X.X.x) will: 33 | * Perform Tests 34 | * Publish a snapshot build (currently to GitHub) 35 | * Generate documentation 36 | * Publish documentation to the snapshot location if it's on the latest branch. 37 | 38 | Releases 39 | --- 40 | To perform a release: 41 | * Draft a release announcement on GitHub. 42 | * `Choose a tag` - enter `v` + the desired project version & select the `Create new tag: ` option. i.e. `v6.0.0` 43 | * `Target` - choose the major release branch. 44 | * The `Release title` should be the major version without the `v` 45 | * Add a description for the release. 46 | * Select `Publish Release` 47 | * On publish of a new release, the `Release` action will kick off. It will do the following: 48 | * (#1) Publish Job: 49 | * Perform `pre-release` steps: 50 | * Changes `gradle.properties` projectVersion based on the release. 51 | * Commits the change 52 | * Builds the project 53 | * Publishes to the Staging repository and closes the staging repository. 54 | * (#2) Release Job: 55 | * Releases the staging repository & closes it so the artifact is available on Maven Central. 56 | * Perform `post-release` steps: 57 | * Closes any open milestones associated to the major release 58 | * Creates the next milestone 59 | * Modifies `gradle.properties` to go back to the next snapshot version. 60 | * Commits the `gradle.properties` changes. 61 | * (#3) Documentation: 62 | * Generates the documentation 63 | * Publishes the documentation 64 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url "https://repo.grails.org/grails/core/" } 4 | } 5 | dependencies { 6 | classpath "org.grails:grails-gradle-plugin:${grailsGradlePluginVersion}" 7 | } 8 | } 9 | 10 | plugins { 11 | id 'com.adarshr.test-logger' version '4.0.0' 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | maven { url "https://repo.grails.org/grails/core" } 17 | } 18 | } 19 | 20 | version project.projectVersion 21 | group 'io.github.longwa' 22 | 23 | subprojects { 24 | version project.projectVersion 25 | group 'io.github.longwa' 26 | 27 | apply plugin: 'groovy' 28 | 29 | project.compileJava.options.release = 17 30 | 31 | apply plugin: 'com.adarshr.test-logger' 32 | testlogger { 33 | theme 'mocha' 34 | showFullStackTraces true 35 | showStandardStreams true 36 | showPassedStandardStreams false 37 | showSkippedStandardStreams false 38 | showFailedStandardStreams true 39 | } 40 | 41 | test.testLogging { 42 | events "failed" 43 | exceptionFormat "full" 44 | } 45 | 46 | tasks.withType(Test).configureEach { testPlatform -> 47 | useJUnitPlatform() 48 | 49 | // GitHub actions does not have the country set, only the language so force both here for tests to pass and be consistent 50 | systemProperty 'user.country', 'US' 51 | systemProperty 'user.language', 'en' 52 | } 53 | 54 | if (project.name == 'build-test-data') { 55 | apply plugin: "org.grails.grails-publish" 56 | grailsPublish { 57 | githubSlug = 'longwa/build-test-data' 58 | artifactId = 'build-test-data' 59 | groupId = 'io.github.longwa' 60 | license { 61 | name = 'Apache-2.0' 62 | } 63 | title = 'Build Test Data Grails Plugin' 64 | desc = 'Enables the easy creation of test data by automatically satisfying common constraints' 65 | developers = [longwa: 'Aaron Long', tednaleid: 'Ted Naleid', basejump: 'Joshua Burnett'] 66 | } 67 | } 68 | } 69 | 70 | apply from: rootProject.file("gradle/docs.gradle") -------------------------------------------------------------------------------- /docs/build.gradle: -------------------------------------------------------------------------------- 1 | import org.asciidoctor.gradle.jvm.AsciidoctorTask 2 | 3 | String getGrailsDocumentationVersion(String version) { 4 | if(version.endsWith('-SNAPSHOT')) { 5 | return 'snapshot' 6 | } 7 | 8 | return version 9 | } 10 | 11 | plugins { 12 | id 'org.asciidoctor.jvm.convert' version "4.0.3" 13 | } 14 | 15 | def asciidoctorAttributes = [ 16 | 'source-highlighter': 'coderay', 17 | toc : 'left', 18 | toclevels : '2', 19 | 'toc-title' : 'Table of Contents', 20 | icons : 'font', 21 | id : project.name + ':' + project.version, 22 | idprefix : '', 23 | idseparator : '-', 24 | version : project.version, 25 | projectUrl : "https://github.com/longwa/build-test-data", 26 | sourcedir : "${rootProject.allprojects.find { it.name == 'build-test-data' }.projectDir}/src/main/groovy", 27 | gormDetailedLink : "https://gorm.grails.org/${getGrailsDocumentationVersion(project['gorm.version'] as String)}/hibernate/manual/index.html", 28 | gormSummaryLink : "https://docs.grails.org/${getGrailsDocumentationVersion(project.grailsVersion)}/guide/GORM.html", 29 | grailsDocBase : "https://docs.grails.org/${getGrailsDocumentationVersion(project.grailsVersion)}" 30 | ] 31 | 32 | tasks.named('asciidoctor', AsciidoctorTask) { AsciidoctorTask it -> 33 | it.jvm { 34 | jvmArgs("--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED") 35 | } 36 | 37 | it.baseDirFollowsSourceDir() 38 | it.sourceDir project.file('src/docs') 39 | it.sources { include 'index.adoc' } 40 | it.outputDir = project.buildDir.toPath().resolve('docs').toFile() 41 | it.options doctype: 'book' 42 | it.attributes asciidoctorAttributes 43 | } 44 | -------------------------------------------------------------------------------- /docs/src/docs/examples.adoc: -------------------------------------------------------------------------------- 1 | [[examples]] 2 | == Examples 3 | Once you have the plugin installed, it should be as easy as calling `build()` on any of your domain objects in your tests. 4 | 5 | If you don't pass any parameters to build, it will attempt to provide values for all of the properties that it determines have required values based on the constraints. This includes other domain objects. 6 | 7 | If you want to specify particular values, you can pass build a map (just like you can to a domain object constructor). The plugin will attempt to create new values for any required properties that you don't specify in the map. 8 | ```groovy 9 | domainObject.build(myProperty:'myPropertyValue') 10 | ``` 11 | The build method default is to attempt to save the populated domain object. You can avoid saving the object first by calling the buildWithoutSave method: 12 | ```groovy 13 | domainObject.buildWithoutSave(myProperty:'myPropertyValue') 14 | ``` 15 | 16 | === Working with Unique Constraints 17 | There currently isn't built-in support for uniqueness constraints, but it's pretty easy to get this working using a closure in the config file to generate a series of unique values. 18 | ```groovy 19 | testDataConfig { 20 | sampleData { 21 | 'com.foo.Book' { 22 | def i = 1 23 | title = {-> "title${i++}" } // creates "title1", "title2",... 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | If your test cares about this unique value, you'll likely want to reset the configuration either directly in your test or in the setUp of the test. Otherwise, other tests could have grabbed the value that you were expecting. To reset the configuration back to the original state, use this: 30 | 31 | ```groovy 32 | TestDataConfigurationHolder.reset() 33 | ``` 34 | 35 | === Building Objects and Building Associated Child Objects 36 | When you have a parent object that you want to have specific child objects, here's one way this can be done. 37 | ```groovy 38 | class Issue { 39 | String title 40 | static hasMany = [pages: Page] 41 | } 42 | 43 | class Page { 44 | Integer number 45 | static hasMany = [advertisments: Advertisment] 46 | static belongsTo = [issue: Issue] 47 | } 48 | ``` 49 | 50 | You can create an issue and then give it pages: 51 | ```groovy 52 | // Build the issue first 53 | def issue = Issue.build() 54 | 55 | (1..5).collect { Page.build(issue: issue, number: it) } 56 | 57 | // Pages are automatically added to the issue's hasMany pages collection 58 | assertEquals 5, issue.pages.size() 59 | 60 | // Don't build extra issues when building pages 61 | assertEquals 1, Issue.list().size() 62 | ``` 63 | 64 | We don't want to build the Page objects before we have the Issue otherwise the build method will create dummy issues in the database for each page (and that last assert would fail). 65 | 66 | === Assigning System Wide Default Values 67 | Having build assign system wide default property values, or give values to classes that are "distant" in the object graph from the object you're building can all be done with the [[TestDataConfig]] configuration file. 68 | 69 | === Logging Information 70 | If you want to see the detail of what objects the plugin is trying to save, you can set the logging level for the DomainTestDataService and the DomainInstanceBuilder to info. Just stick this line in your 'logback.groovy' 71 | ```groovy 72 | logger("grails.buildtestdata.DomainInstanceBuilder", INFO) 73 | ``` 74 | If you want to see all of the gory detail about what the plugin is doing as it traverses the object graph and examines each property, set the logging level to debug and add in the property handlers: 75 | ```groovy 76 | logger("grails.buildtestdata", DEBUG) 77 | ``` -------------------------------------------------------------------------------- /docs/src/docs/features.adoc: -------------------------------------------------------------------------------- 1 | [[features]] 2 | == Features 3 | Auto generation of grails property types, including: 4 | 5 | - {gormSummaryLink}[Domain objects] using one-to-one, one-to-many, and many-to-many relationships. 6 | - {gormSummaryLink}[Embedded domain objects] 7 | - String 8 | - Date 9 | - Boolean 10 | - Numbers (Integer, Long, Float, Short, etc) 11 | - Byte 12 | - JodaTime classes 13 | - Any persistable class with a zero-argument constructor 14 | 15 | === Supported Constraints 16 | - {grailsDocBase}/ref/Constraints/nullable.html[nullable] 17 | - {grailsDocBase}/ref/Constraints/blank.html[blank] 18 | - {grailsDocBase}/ref/Constraints/creditCard.html[creditCard] 19 | - {grailsDocBase}/ref/Constraints/email.html[email] 20 | - {grailsDocBase}/ref/Constraints/inList.html[inList] 21 | - {grailsDocBase}/ref/Constraints/max.html[max] 22 | - {grailsDocBase}/ref/Constraints/maxSize.html[maxSize] 23 | - {grailsDocBase}/ref/Constraints/min.html[min] 24 | - {grailsDocBase}/ref/Constraints/minSize.html[minSize] 25 | - {grailsDocBase}/ref/Constraints/range.html[range] 26 | - {grailsDocBase}/ref/Constraints/size.html[size] 27 | - {grailsDocBase}/ref/Constraints/url.html[url] 28 | - {grailsDocBase}/ref/Constraints/matches.html[matches] 29 | 30 | === Unsupported Constraints 31 | - {grailsDocBase}/ref/Constraints/scale.html[scale] 32 | - {grailsDocBase}/ref/Constraints/validator.html[validator] - custom validators can have all kinds of business logic in them and there isn't a programmatic way to interrogate this. If the test value that's tried doesn't pass your logic, you'll need to provide a value, or mock out the calls that the validator makes so that it passes. 33 | - {grailsDocBase}/ref/Constraints/unique.html[unique] - not directly supported but it's possible to specify your own unique value in a config file as demonstrated on the link:examples[Examples] page. 34 | - {grailsDocBase}/ref/Constraints/notEqual.html[notEqual] - unlikely that our test data would match this, could be supported in the future 35 | 36 | -------------------------------------------------------------------------------- /docs/src/docs/index.adoc: -------------------------------------------------------------------------------- 1 | = Build Test Data Users Guide 2 | :source-highlighter: coderay 3 | :numbered: 4 | 5 | include::introduction.adoc[] 6 | include::methodology.adoc[] 7 | include::features.adoc[] 8 | include::installation.adoc[] 9 | include::upgrading.adoc[] 10 | include::usage.adoc[] 11 | include::unittesting.adoc[] 12 | include::integrationtesting.adoc[] 13 | include::testdataconfig.adoc[] 14 | include::examples.adoc[] 15 | -------------------------------------------------------------------------------- /docs/src/docs/index.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Build Test Data - Grails Plugin 8 | 9 | 58 | 59 | 60 | 61 | 62 | 63 | Fork me on GitHub 64 | 65 | 66 |
67 |

Build Test Data - Grails Plugin

68 | 69 |
70 | 71 |

Build Test Data - SNAPSHOT

72 | 76 | 77 |

Build Test Data - 6.0.0-M1 (Grails 7+)

78 | 81 | 82 |

Build Test Data - 5.0.0 (Grails 5 & 6)

83 | 86 |
87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /docs/src/docs/installation.adoc: -------------------------------------------------------------------------------- 1 | [[installation]] 2 | == Installation 3 | The test framework and testing methodology for Grails has undergone several major revisions over the past few releases. 4 | 5 | Unfortunately, this makes it impossible to have one plugin which is compatible with all prior versions. 6 | 7 | === Grails 7.0 or later 8 | `testImplementation "io.github.longwa:build-test-data:{version}"` 9 | 10 | === Grails 5.0 or Grails 6.0 11 | `testImplementation "io.github.longwa:build-test-data:5.0.0"` 12 | 13 | === Grails 4.0 14 | `testCompile "org.grails.plugins:build-test-data:4.0.0"` 15 | 16 | === Grails 3.3 17 | `testCompile "org.grails.plugins:build-test-data:3.3.1"` 18 | 19 | === Grails 3.1 and 3.2 20 | `testCompile "org.grails.plugins:build-test-data:3.0.1"` 21 | 22 | NOTE: Version 3.0.x is required for Grails versions 3.0 through 3.2 23 | 24 | See the legacy Wiki documentation for details: 25 | {projectUrl}/wiki 26 | 27 | === Grails 2.x 28 | See the legacy Wiki documentation for details: 29 | {projectUrl}/wiki 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/src/docs/integrationtesting.adoc: -------------------------------------------------------------------------------- 1 | [[integrationtesting]] 2 | == Integration Testing 3 | Integration tests don't require the same type of setup as Unit Tests since the Grails environment is fully bootstrapped. 4 | 5 | The plugin includes a trait that should be mixed into Integration tests to provide additional functionality: 6 | 7 | trait TestDataBuilder 8 | 9 | [NOTE] 10 | The trait is not strictly required, but it does include some setup and cleanup functionality that could be useful in some cases. Additional functionality may be added in the future via this trait, so implementing it is preferred. 11 | 12 | === Using Build Annotation 13 | In prior versions, the `@Build` annotation was *strictly* for Unit Tests (it would cause numerous issues when used on integration tests, which was a common source of confusion on our projects). 14 | 15 | Starting with build-test-data 3.3, the `@Build` annotation can be used safely on integration tests. The primary reason to use it would be to take advantage of IDE autocomplete for properties when calling the `DomainObject.build()` method. 16 | -------------------------------------------------------------------------------- /docs/src/docs/introduction.adoc: -------------------------------------------------------------------------------- 1 | [[introduction]] 2 | == Introduction 3 | Creating maintainable test data is hard. Often an entire object graph needs to be created to support the instantiation of a single domain object. This leads to either the cutting and pasting of that creation code, or relying on a canned set of objects that we've grown over time and maintained as the domain objects change. After a while, adding just one more widget to that set of canned data ends up breaking tests just about every time. 4 | 5 | There has to be a better solution, right? 6 | 7 | Yep! Due to the power and the glory of Grails, we have a lot of metadata at our fingertips about those domain objects. We know what constraints we've placed on our objects, and which objects depend on other objects to live. 8 | 9 | Using this additional information, we've created a grails plugin that makes it easy to just provide those values that you want to exercise under test and not worry about the rest of the object graph that you need to create just to instantiate your domain objects. 10 | 11 | Once installed, all you have to do is call the new "build" method on your domain class and you'll be given a valid instance with all of the required constraints given values. 12 | 13 | ```groovy 14 | def author = Author.build(name: 'Ted Naleid') 15 | //or 16 | def author = TestData.build(Author, name: 'Ted Naleid') 17 | ``` 18 | 19 | === Plugin Objectives 20 | 21 | * The definition of the domain objects under test should be next to the test code, this improves test comprehension. 22 | 23 | * You should only need to create those fields and objects that are pertinent to the test. Other test setup is noise that obfuscates the meaning of the test. 24 | 25 | * Tests should not be dependent on other tests, only on the code under test. Therefore, the same test data should not be used by multiple tests, this creates a strong coupling and leads to test fragility. 26 | 27 | * Changes to domain objects that do not affect the the code under test should not break the test. 28 | 29 | === Release History 30 | * Grails 7+ - Build Test Data 6.x 31 | * Grails 5.x to 6.x - Build Test Data 5.x 32 | * Grails 3.3 to 4.x - Build Test Data 4.x 33 | * Grails 2.4.x to 3.2.x - Build Test Data 3.x 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/src/docs/methodology.adoc: -------------------------------------------------------------------------------- 1 | [[methodology]] 2 | == Methodology 3 | Over the years, a number of different methods for creating and maintaining testing data have been tried. Here's a subset of the solutions along with some pros/cons to each of them. 4 | 5 | === Manual Data Creation 6 | Creating testing data for integration tests has gone through an evolution over the years. 7 | 8 | It first started with all of the test data being constructed right in the body of the test method, leading to a lot of cut and paste when you hit another method. 9 | 10 | ==== Strengths 11 | * the code that defines the objects under test is right next to the code that uses those objects in test. 12 | 13 | ==== Weaknesses 14 | * If the objects under test rely on other objects, it can be necessary to create a large graph of objects in your test method 15 | * Not at all DRY, cutting and pasting leads to errors and extra code that you don't need 16 | * When you add a new method to an existing object (or change/delete an existing one), you need to change all of the cut/pasted lines in all of the test methods 17 | 18 | JUnit (and other xUnit) testing frameworks DRYed this up by having a "setUp" method that gets called for all test methods, but this requires you to share data across all of the tests in the test class and you're still manually creating it in each test 19 | 20 | === Test Fixtures 21 | Text fixtures allow you to use a DSL to define a named object graph. You can then refer to this object graph in a test to have the fixtures instantiated and saved to the database before running your test. 22 | 23 | ==== Strengths 24 | * https://github.com/gpc/fixtures[Fixture Plugin] 25 | * Full control over values in every object in the object graph 26 | 27 | ==== Weaknesses 28 | * Removes definition of testing data from test which can hurt test clarity. Who knows how many family members the `johnsonFamily` fixture has and how many the `smithFamily` has? You have to look at another file to find out. 29 | * Starts out well, but more and more tests start to rely on the same fixtures making them fragile. "I'll just add another Widget to this fixture" ends up breaking 10 other tests. 30 | * Any changes to domain objects necessitate modifying every fixture that has that domain object within the object graph 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/src/docs/upgrading.adoc: -------------------------------------------------------------------------------- 1 | [[upgrading]] 2 | == Upgrading 3 | When upgrading from older versions of Grails, first follow the standard Grails upgrade process. 4 | 5 | === Performance Improvements 6 | The core plugin has been refactored to reduce metaClass dependency and remove a few poor performing contructs (I'm looking at you ClassPathScanningCandidateComponentProvider). 7 | 8 | Most of the plugin classes have also been converted to `@CompileStatic`. In addition, the DomainInstanceBuilder can be cached and re-used to prevent additional overhead. 9 | 10 | === Abstract Classes 11 | Concrete subclasses are no longer discovered automatically. If you graph needs to build an abstract class, use the `abstractDefaults` parameter in `TestDataConfig` to specify a concrete subclass. 12 | 13 | ```groovy 14 | abstractDefault = [ 15 | 'test.AbstractBook' : MyBook, 16 | 'test.AbstractAuthor' : Tolkien 17 | ] 18 | ``` 19 | 20 | === Config Parameters 21 | Some test data config parameters such as `abstractDefault` and `sampleData` used to allow either a Class or String as the map key. 22 | 23 | In 3.3 and later, only a full package name String is allowed. 24 | 25 | === Disabling Via TestDataConfig 26 | You can no longer disable via the `testDataConfig.enabled = false` option. 27 | 28 | === Build methods refactored 29 | They all go through static helpers in the TestData eventually 30 | `buildLazy` -> `findOrBuild` or `build(find:true)` 31 | `buildWithoutSave` -> `build(save: false)` 32 | 33 | === Unit Tests 34 | Most of the work when upgrading to Grails 3.3.x will involve refactoring related to the framework itself with only minor changes for build-test-data. 35 | 36 | --- 37 | ```groovy 38 | @Build([Author]) 39 | class AuthorSpec extends Specification { 40 | ... 41 | } 42 | ``` 43 | 44 | Just add the trait: 45 | ```groovy 46 | @Build([Author]) 47 | class AuthorSpec extends Specification implements BuildDataTest { 48 | ... 49 | } 50 | ``` 51 | 52 | Optionally, use the standard Grails mock method: 53 | 54 | ```groovy 55 | class AuthorSpec extends Specification implements BuildDataTest { 56 | void setupSpec() { 57 | mockDomains(Author) 58 | } 59 | ... 60 | } 61 | ``` 62 | 63 | or by overriding the *getDomainClassesToMock* method: 64 | 65 | ```groovy 66 | class AuthorSpec extends Specification implements BuildDataTest { 67 | Class[] getDomainClassesToMock() { 68 | [Author] 69 | } 70 | ... 71 | } 72 | ``` 73 | 74 | or use with the BuildDomainTest and generics 75 | ```groovy 76 | class AuthorSpec extends Specification implements BuildDomainTest{ 77 | ... 78 | } 79 | ``` 80 | 81 | WARNING: Make sure you use *DomainUnitTest* for unit tests and *TestDataBuilder* for integration tests. 82 | 83 | === Integration Tests 84 | For integration tests, follow the standard Grails upgrade instructions and then add the `TestDataBuilder` trait: 85 | 86 | ```groovy 87 | @Integration 88 | @Rollback 89 | class AuthorIntegrationSpec extends Specification implements TestDataBuilder { 90 | ... 91 | } 92 | ``` 93 | 94 | -------------------------------------------------------------------------------- /examples/alternativeConfig/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url "https://repo.grails.org/grails/core" } 4 | } 5 | dependencies { 6 | classpath "org.grails:grails-gradle-plugin:$grailsGradlePluginVersion" 7 | classpath "org.grails.plugins:hibernate5:${project.'gorm.version' - ".RELEASE"}" 8 | } 9 | } 10 | 11 | plugins { 12 | id "groovy" 13 | id "org.grails.grails-web" 14 | id "org.grails.grails-gsp" 15 | id "war" 16 | id "idea" 17 | // Not needed because no /assets directory 18 | // id "com.bertramlabs.asset-pipeline" version "${assetPipelineVersion}" 19 | id "eclipse" 20 | } 21 | 22 | version "0.1" 23 | group "alternativeconfig" 24 | 25 | dependencies { 26 | implementation project(':build-test-data') 27 | 28 | profile("org.grails.profiles:web") 29 | 30 | implementation("org.grails:grails-core") 31 | implementation("org.grails:grails-logging") 32 | implementation("org.grails:grails-plugin-databinding") 33 | implementation("org.grails:grails-plugin-i18n") 34 | implementation("org.grails:grails-plugin-interceptors") 35 | implementation("org.grails:grails-plugin-rest") 36 | implementation("org.grails:grails-plugin-services") 37 | implementation("org.grails:grails-plugin-url-mappings") 38 | implementation("org.grails:grails-web-boot") 39 | implementation("org.grails.plugins:gsp") 40 | implementation("org.grails.plugins:hibernate5") 41 | implementation("org.grails.plugins:scaffolding") 42 | implementation("org.sitemesh:grails-plugin-sitemesh3:${grailsVersion}") 43 | implementation("org.springframework.boot:spring-boot-autoconfigure") 44 | implementation("org.springframework.boot:spring-boot-starter") 45 | implementation("org.springframework.boot:spring-boot-starter-actuator") 46 | implementation("org.springframework.boot:spring-boot-starter-logging") 47 | implementation("org.springframework.boot:spring-boot-starter-tomcat") 48 | implementation("org.springframework.boot:spring-boot-starter-validation") 49 | 50 | console("org.grails:grails-console") 51 | 52 | runtimeOnly("com.bertramlabs.plugins:asset-pipeline-grails") 53 | runtimeOnly("com.h2database:h2") 54 | runtimeOnly("org.apache.tomcat:tomcat-jdbc") 55 | runtimeOnly("org.fusesource.jansi:jansi") 56 | 57 | integrationTestImplementation testFixtures("org.grails.plugins:geb") 58 | 59 | testImplementation("org.grails:grails-gorm-testing-support") 60 | testImplementation("org.grails:grails-web-testing-support") 61 | testImplementation("org.spockframework:spock-core") 62 | } 63 | 64 | // No assets in test project 65 | //assets { 66 | // minifyJs = true 67 | // minifyCss = true 68 | //} -------------------------------------------------------------------------------- /examples/alternativeConfig/grails-app/conf/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/alternativeConfig/grails-app/conf/spring/resources.groovy: -------------------------------------------------------------------------------- 1 | package spring 2 | // Place your Spring DSL code here 3 | beans = { 4 | } 5 | -------------------------------------------------------------------------------- /examples/alternativeConfig/grails-app/domain/alternativeconfig/Simple.groovy: -------------------------------------------------------------------------------- 1 | package alternativeconfig 2 | 3 | class Simple { 4 | String name 5 | static constraints = { 6 | name nullable: false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/alternativeConfig/grails-app/i18n/messages.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/longwa/build-test-data/a13892cb986ef38dd0cfb4eca8f0cc8c944b5a22/examples/alternativeConfig/grails-app/i18n/messages.properties -------------------------------------------------------------------------------- /examples/alternativeConfig/grails-app/init/BootStrap.groovy: -------------------------------------------------------------------------------- 1 | class BootStrap { 2 | 3 | def init = { servletContext -> 4 | } 5 | def destroy = { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/alternativeConfig/grails-app/init/alternativeconfig/Application.groovy: -------------------------------------------------------------------------------- 1 | package alternativeconfig 2 | 3 | import grails.boot.GrailsApp 4 | import grails.boot.config.GrailsAutoConfiguration 5 | 6 | class Application extends GrailsAutoConfiguration { 7 | static void main(String[] args) { 8 | GrailsApp.run(Application, args) 9 | } 10 | } -------------------------------------------------------------------------------- /examples/alternativeConfig/src/test/groovy/alternativeconfig/SimpleSpec.groovy: -------------------------------------------------------------------------------- 1 | package alternativeconfig 2 | 3 | import grails.buildtestdata.BuildDataTest 4 | import grails.util.Holders 5 | import spock.lang.Specification 6 | 7 | class SimpleSpec extends Specification implements BuildDataTest { 8 | void setupSpec() { 9 | mockDomains(Simple) 10 | } 11 | 12 | void "test that build is done with the alternative config file"() { 13 | expect: 14 | Holders.config.grails.buildtestdata.testDataConfig == "AlternativeTestDataConfig" 15 | 16 | and: 17 | build(Simple).name == "Alternative name" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/alternativeConfig/src/test/resources/AlternativeTestDataConfig.groovy: -------------------------------------------------------------------------------- 1 | testDataConfig { 2 | sampleData { 3 | // Simple class is in "alternativeconfig" package so we use a string in the builder 4 | 'alternativeconfig.Simple' { 5 | name = "Alternative name" 6 | } 7 | } 8 | } 9 | 10 | // if you'd like to disable the build-test-data plugin in an environment, just set 11 | // the "enabled" property to false 12 | 13 | environments { 14 | production { 15 | testDataConfig { 16 | enabled = false 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /examples/bookStore/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url "https://repo.grails.org/grails/core" } 4 | } 5 | dependencies { 6 | classpath "org.grails:grails-gradle-plugin:$grailsGradlePluginVersion" 7 | } 8 | } 9 | 10 | plugins { 11 | id "groovy" 12 | id "org.grails.grails-web" 13 | id "org.grails.grails-gsp" 14 | id "war" 15 | id "idea" 16 | // Not needed because no /assets directory 17 | // id "com.bertramlabs.asset-pipeline" version "${assetPipelineVersion}" 18 | id "eclipse" 19 | } 20 | 21 | version "0.1" 22 | group "bookStore" 23 | 24 | dependencies { 25 | profile("org.grails.profiles:web") 26 | 27 | implementation("org.grails:grails-core") 28 | implementation("org.grails:grails-logging") 29 | implementation("org.grails:grails-plugin-databinding") 30 | implementation("org.grails:grails-plugin-i18n") 31 | implementation("org.grails:grails-plugin-interceptors") 32 | implementation("org.grails:grails-plugin-rest") 33 | implementation("org.grails:grails-plugin-services") 34 | implementation("org.grails:grails-plugin-url-mappings") 35 | implementation("org.grails:grails-web-boot") 36 | implementation("org.grails.plugins:gsp") 37 | implementation("org.grails.plugins:hibernate5") 38 | implementation("org.grails.plugins:scaffolding") 39 | implementation("org.springframework.boot:spring-boot-autoconfigure") 40 | implementation("org.springframework.boot:spring-boot-starter") 41 | implementation("org.springframework.boot:spring-boot-starter-actuator") 42 | implementation("org.springframework.boot:spring-boot-starter-logging") 43 | implementation("org.springframework.boot:spring-boot-starter-tomcat") 44 | implementation("org.springframework.boot:spring-boot-starter-validation") 45 | 46 | console("org.grails:grails-console") 47 | 48 | runtimeOnly("com.bertramlabs.plugins:asset-pipeline-grails") 49 | runtimeOnly("com.h2database:h2") 50 | runtimeOnly("org.apache.tomcat:tomcat-jdbc") 51 | runtimeOnly("org.fusesource.jansi:jansi") 52 | 53 | integrationTestImplementation testFixtures("org.grails.plugins:geb") 54 | 55 | testImplementation("org.grails:grails-gorm-testing-support") 56 | testImplementation("org.grails:grails-web-testing-support") 57 | testImplementation("org.spockframework:spock-core") 58 | 59 | testImplementation project(':build-test-data') 60 | } 61 | 62 | // No assets in test project 63 | //assets { 64 | // minifyJs = true 65 | // minifyCss = true 66 | //} 67 | 68 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/conf/application.yml: -------------------------------------------------------------------------------- 1 | --- 2 | grails: 3 | profile: web 4 | codegen: 5 | defaultPackage: bookstore 6 | info: 7 | app: 8 | name: '@info.app.name@' 9 | version: '@info.app.version@' 10 | grailsVersion: '@info.app.grailsVersion@' 11 | spring: 12 | groovy: 13 | template: 14 | check-template-location: false 15 | 16 | --- 17 | grails: 18 | mime: 19 | disable: 20 | accept: 21 | header: 22 | userAgents: 23 | - Gecko 24 | - WebKit 25 | - Presto 26 | - Trident 27 | types: 28 | all: '*/*' 29 | atom: application/atom+xml 30 | css: text/css 31 | csv: text/csv 32 | form: application/x-www-form-urlencoded 33 | html: 34 | - text/html 35 | - application/xhtml+xml 36 | js: text/javascript 37 | json: 38 | - application/json 39 | - text/json 40 | multipartForm: multipart/form-data 41 | pdf: application/pdf 42 | rss: application/rss+xml 43 | text: text/plain 44 | hal: 45 | - application/hal+json 46 | - application/hal+xml 47 | xml: 48 | - text/xml 49 | - application/xml 50 | urlmapping: 51 | cache: 52 | maxsize: 1000 53 | controllers: 54 | defaultScope: singleton 55 | converters: 56 | encoding: UTF-8 57 | views: 58 | default: 59 | codec: html 60 | gsp: 61 | encoding: UTF-8 62 | htmlcodec: xml 63 | codecs: 64 | expression: html 65 | scriptlets: html 66 | taglib: none 67 | staticparts: none 68 | endpoints: 69 | jmx: 70 | unique-names: true 71 | --- 72 | hibernate: 73 | cache: 74 | queries: false 75 | use_second_level_cache: false 76 | use_query_cache: false 77 | dataSource: 78 | pooled: true 79 | jmxExport: true 80 | driverClassName: org.h2.Driver 81 | username: sa 82 | password: '' 83 | 84 | environments: 85 | development: 86 | dataSource: 87 | dbCreate: create-drop 88 | url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE 89 | test: 90 | dataSource: 91 | dbCreate: update 92 | url: jdbc:h2:mem:testDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE 93 | production: 94 | dataSource: 95 | dbCreate: none 96 | url: jdbc:h2:./prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE 97 | properties: 98 | jmxEnabled: true 99 | initialSize: 5 100 | maxActive: 50 101 | minIdle: 5 102 | maxIdle: 25 103 | maxWait: 10000 104 | maxAge: 600000 105 | timeBetweenEvictionRunsMillis: 5000 106 | minEvictableIdleTimeMillis: 60000 107 | validationQuery: SELECT 1 108 | validationQueryTimeout: 3 109 | validationInterval: 15000 110 | testOnBorrow: true 111 | testWhileIdle: true 112 | testOnReturn: false 113 | jdbcInterceptors: ConnectionState 114 | defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED 115 | 116 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/conf/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/conf/spring/resources.groovy: -------------------------------------------------------------------------------- 1 | package spring 2 | // Place your Spring DSL code here 3 | beans = { 4 | 5 | } -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/abstractclass/AbstractClass.groovy: -------------------------------------------------------------------------------- 1 | package abstractclass 2 | 3 | abstract class AbstractClass { 4 | String abstractAttribute 5 | 6 | String toString() { 7 | "ConcreteSubClass: $abstractAttribute" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/abstractclass/AbstractSubClass.groovy: -------------------------------------------------------------------------------- 1 | package abstractclass 2 | 3 | // Another abstract class in the hierarchy, just to make things interesting 4 | abstract class AbstractSubClass extends AbstractClass { 5 | String subClassAttribute 6 | 7 | String toString() { 8 | "AbstractSubClass: $subClassAttribute" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/abstractclass/AnotherConcreteSubClass.groovy: -------------------------------------------------------------------------------- 1 | package abstractclass 2 | 3 | // Concrete implementation 4 | class AnotherConcreteSubClass extends AbstractSubClass { 5 | String concreteClassAttribute 6 | 7 | String toString() { 8 | "AnotherConcreteSubClass: $concreteClassAttribute" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/abstractclass/ConcreteSubClass.groovy: -------------------------------------------------------------------------------- 1 | package abstractclass 2 | 3 | // Concrete implementation 4 | class ConcreteSubClass extends AbstractSubClass { 5 | String concreteClassAttribute 6 | 7 | String toString() { 8 | "ConcreteSubClass: $concreteClassAttribute" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/abstractclass/RelatedToAbstract.groovy: -------------------------------------------------------------------------------- 1 | package abstractclass 2 | 3 | class RelatedToAbstract { 4 | String name 5 | AbstractClass genericParent 6 | } 7 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/bookstore/Address.groovy: -------------------------------------------------------------------------------- 1 | package bookstore 2 | 3 | import embedded.LatLon 4 | 5 | class Address { 6 | String address1 7 | String address2 8 | String city 9 | String state 10 | String zip 11 | String emailAddress 12 | String webSite 13 | 14 | LatLon latLon 15 | LatLon altLatLon 16 | 17 | static embedded = ['latLon', 'altLatLon'] 18 | 19 | static constraints = { 20 | address1(maxSize: 55) 21 | address2(maxSize: 55, nullable: true) 22 | city(maxSize: 30) 23 | state(maxSize: 30) 24 | zip(matches: /\d{5}/, nullable: true) 25 | emailAddress(email: true, minSize: 40) 26 | webSite(url: true, minSize: 40) 27 | latLon(nullable: true) 28 | altLatLon(nullable: true) 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/bookstore/Author.groovy: -------------------------------------------------------------------------------- 1 | package bookstore 2 | 3 | class Author { 4 | String firstName 5 | String middleInitial 6 | String lastName 7 | Date dateOfBirth 8 | String gender 9 | Address address 10 | 11 | static hasMany = [books: Book] 12 | 13 | static constraints = { 14 | firstName(nullable: false, minSize: 1, maxSize: 25, blank: false) 15 | middleInitial(nullable: true, maxSize: 1) 16 | lastName(nullable: false, minSize: 1, maxSize: 35, blank: false) 17 | gender(blank: false, nullable: false, maxSize: 6, inList: ['male', 'female']) 18 | address(nullable: false) 19 | books(nullable: false, minSize: 2) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/bookstore/Book.groovy: -------------------------------------------------------------------------------- 1 | package bookstore 2 | 3 | import bookstore.Author 4 | 5 | class Book { 6 | String title 7 | String isbn 8 | Date published 9 | 10 | static belongsTo = [author: Author] 11 | static hasMany = [invoices: Invoice] 12 | 13 | } 14 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/bookstore/BookInfo.groovy: -------------------------------------------------------------------------------- 1 | package bookstore 2 | 3 | import enums.BookType 4 | 5 | class BookInfo { 6 | BookType primaryType 7 | 8 | // Basic collections, one of Enum type which is even more special 9 | static hasMany = [bookTypes: BookType, alternateNames: String] 10 | 11 | static constraints = { 12 | bookTypes(nullable: false, minSize: 1) 13 | alternateNames(nullable: false, minSize: 1) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/bookstore/Customer.groovy: -------------------------------------------------------------------------------- 1 | package bookstore 2 | class Customer { 3 | String firstName 4 | String middleInitial 5 | String lastName 6 | Address address 7 | 8 | static hasMany = [invoices: Invoice] 9 | 10 | static constraints = { 11 | firstName(nullable: false, minSize: 1, maxSize: 25, blank: false) 12 | middleInitial(nullable: true, maxSize: 1) 13 | lastName(nullable: false, minSize: 1, maxSize: 35, blank: false) 14 | address(nullable: true) 15 | } 16 | 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/bookstore/EstablishedAuthor.groovy: -------------------------------------------------------------------------------- 1 | package bookstore 2 | 3 | class EstablishedAuthor { 4 | 5 | String name 6 | Set hardcoverBooks 7 | List paperbackBooks 8 | Map metaData 9 | 10 | static hasMany = [hardcoverBooks: Book, paperbackBooks: Book] 11 | 12 | static constraints = { 13 | hardcoverBooks(nullable: false, minSize: 1) 14 | paperbackBooks(nullable: false, minSize: 1) 15 | metaData nullable: false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/bookstore/Invoice.groovy: -------------------------------------------------------------------------------- 1 | package bookstore 2 | 3 | class Invoice { 4 | String cardNumber 5 | String departmentCode 6 | 7 | static hasMany = [books: Book] 8 | static belongsTo = [Customer, Book] 9 | 10 | // Minimum order of 3, heh 11 | static constraints = { 12 | cardNumber(creditCard:true) 13 | books(nullable: false, minSize: 3) 14 | departmentCode(matches: '[A-Z]{2}[0-9]{4}') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/bookstore/Payment.groovy: -------------------------------------------------------------------------------- 1 | package bookstore 2 | 3 | class Payment { 4 | Date paid 5 | Float total 6 | 7 | static belongsTo = [invoice: Invoice] 8 | 9 | } 10 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/config/Article.groovy: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | class Article { 4 | String name 5 | static constraints = { 6 | name(unique: true) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/config/Hotel.groovy: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // for use in exercising the grails-app/conf/TestDatConfig.groovy 4 | class Hotel { 5 | String name 6 | String faxNumber 7 | static constraints = { 8 | name( blank:false, validator: { value, obj -> 9 | return ["Motel 6", "Super 8", "Holiday Inn", "Hilton", "Westin"].contains(value) 10 | }) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/embedded/Embedding.groovy: -------------------------------------------------------------------------------- 1 | package embedded 2 | 3 | class Embedding { 4 | String name 5 | Embedded inner 6 | 7 | // grails unfortunately doesn't create domain artefacts for embedded objects created outside of grails-app/domain 8 | // for those, you'll need to define an explicit embedded object example in TestDataConfig to get it populated 9 | static embedded = ['inner'] 10 | } 11 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/enumtest/Car.groovy: -------------------------------------------------------------------------------- 1 | package enumtest 2 | 3 | class Car { 4 | CarStatus status 5 | } 6 | 7 | enum CarStatus { REVERSE, PARK, NEUTRAL, DRIVE } 8 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/enumtest/Door.groovy: -------------------------------------------------------------------------------- 1 | package enumtest 2 | 3 | class Door { 4 | DoorStatus status 5 | } 6 | 7 | 8 | enum DoorStatus { 9 | OPEN("Open"), 10 | CLOSED("Closed") 11 | 12 | String status 13 | 14 | DoorStatus(String status) { 15 | this.status = status 16 | } 17 | 18 | static list() { 19 | EnumSet.allOf(DoorStatus) 20 | } 21 | 22 | // this blows up... 23 | static get(String s) { 24 | EnumSet.allOf(DoorStatus.class).find { 25 | it.status == s 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/hibernate4/Gallery.groovy: -------------------------------------------------------------------------------- 1 | package hibernate4 2 | 3 | class Gallery { 4 | String name 5 | static hasMany = [paintings: Painting] 6 | } 7 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/hibernate4/Painter.groovy: -------------------------------------------------------------------------------- 1 | package hibernate4 2 | 3 | class Painter { 4 | String name 5 | 6 | static hasMany = [paintings: Painting] 7 | } 8 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/hibernate4/Painting.groovy: -------------------------------------------------------------------------------- 1 | package hibernate4 2 | 3 | class Painting { 4 | String title 5 | static belongsTo = [painter: Painter, gallery: Gallery] 6 | } 7 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/human/Arm.groovy: -------------------------------------------------------------------------------- 1 | package human 2 | class Arm { 3 | static hasOne = [hand: Hand] 4 | } -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/human/Face.groovy: -------------------------------------------------------------------------------- 1 | package human 2 | class Face { 3 | Nose nose 4 | } 5 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/human/Hand.groovy: -------------------------------------------------------------------------------- 1 | package human 2 | class Hand { 3 | Arm arm 4 | } -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/human/Nose.groovy: -------------------------------------------------------------------------------- 1 | package human 2 | class Nose { 3 | String typeOfNose 4 | static constraints = { 5 | typeOfNose(inList: ['male', 'female']) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/list/Child.groovy: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | class Child { 4 | String name 5 | Date dateOfBirth 6 | Integer grade 7 | 8 | static belongsTo = [santa: Santa] 9 | } 10 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/list/Elf.groovy: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | class Elf { 4 | String name 5 | 6 | static belongsTo = [santa: Santa] 7 | } -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/list/Santa.groovy: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | class Santa { 4 | String firstName = "Santa" 5 | String lastName = "Claus" 6 | 7 | static hasMany = [children: Child, elves: Elf] 8 | 9 | static constraints = { 10 | elves(nullable: false, minSize: 1, validator: { val, obj -> 11 | val.every { it.validate() } 12 | }) 13 | } 14 | } -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/magazine/Advertisment.groovy: -------------------------------------------------------------------------------- 1 | package magazine 2 | 3 | class Advertisment { 4 | String text 5 | } -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/magazine/Issue.groovy: -------------------------------------------------------------------------------- 1 | package magazine 2 | 3 | class Issue { 4 | String title 5 | SortedSet pages 6 | static hasMany = [pages: Page] 7 | } 8 | 9 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/magazine/Page.groovy: -------------------------------------------------------------------------------- 1 | package magazine 2 | 3 | class Page implements Comparable { 4 | Integer number 5 | static hasMany = [advertisments: Advertisment] 6 | static belongsTo = [issue: Issue] 7 | 8 | public int compareTo(Object o) { 9 | return number <=> o.number 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/mapping/AutoTimestampDomain.groovy: -------------------------------------------------------------------------------- 1 | package mapping 2 | 3 | class AutoTimestampDomain { 4 | Date dateCreated 5 | Date lastUpdated 6 | String name 7 | 8 | static mapping = { 9 | autoTimestamp true // populate dateCreated and lastUpdated automatically by grails framework 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/standalone/AssignedKey.groovy: -------------------------------------------------------------------------------- 1 | package standalone 2 | 3 | /** 4 | * Test domain object with assigned key 5 | */ 6 | class AssignedKey { 7 | String id 8 | String attribute 9 | 10 | static constraints = { 11 | id(blank: false) 12 | } 13 | 14 | static mapping = { 15 | id(generator: 'assigned') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/standalone/ChildWithAssignedKey.groovy: -------------------------------------------------------------------------------- 1 | package standalone 2 | 3 | /** 4 | * Test parent domain object with assigned key 5 | */ 6 | class ChildWithAssignedKey extends ParentWithAssignedKey { 7 | String attribute 8 | } 9 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/standalone/ParentWithAssignedKey.groovy: -------------------------------------------------------------------------------- 1 | package standalone 2 | 3 | /** 4 | * Test parent domain object with assigned key 5 | */ 6 | class ParentWithAssignedKey { 7 | String id 8 | 9 | static constraints = { 10 | id(blank: false) 11 | } 12 | 13 | static mapping = { 14 | autoTimestamp(true) 15 | id(generator: 'assigned') 16 | version(true) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/standalone/Standalone.groovy: -------------------------------------------------------------------------------- 1 | package standalone 2 | class Standalone { 3 | // a standalone domain object that doesn't rely on other domain objects, no object graph to create 4 | String name 5 | Integer age 6 | Date created 7 | String emailAddress 8 | 9 | static belongsTo = [parent: Standalone] // shouldn't matter as it's nullable, so it shouldn't get created 10 | 11 | static constraints = { 12 | name(nullable: false) 13 | parent(nullable: true) 14 | emailAddress blank: false, unique: true, email: true 15 | } 16 | 17 | String toString() { "$name" } 18 | } 19 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/subclassing/RelatedClass.groovy: -------------------------------------------------------------------------------- 1 | package subclassing 2 | 3 | class RelatedClass { 4 | 5 | String name 6 | 7 | static hasMany = [superClassInstances: SuperClass] 8 | 9 | String toString() { "RelatedClass: $name" } 10 | } 11 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/subclassing/SubClass.groovy: -------------------------------------------------------------------------------- 1 | package subclassing 2 | 3 | class SubClass extends SuperClass { 4 | String toString() { "SubClass: $name" } 5 | } 6 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/subclassing/SuperClass.groovy: -------------------------------------------------------------------------------- 1 | package subclassing 2 | 3 | class SuperClass { 4 | String name 5 | static belongsTo = [relatedClass: RelatedClass] 6 | 7 | String toString() { "SuperClass: $name" } 8 | } 9 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/triangle/Director.groovy: -------------------------------------------------------------------------------- 1 | package triangle 2 | 3 | class Director { 4 | static hasMany = [workers:Worker, managers:Manager] 5 | } 6 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/triangle/Manager.groovy: -------------------------------------------------------------------------------- 1 | package triangle 2 | 3 | class Manager { 4 | static hasMany = [workers:Worker] 5 | static belongsTo = [director:Director] 6 | } 7 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/domain/triangle/Worker.groovy: -------------------------------------------------------------------------------- 1 | package triangle 2 | 3 | class Worker { 4 | static belongsTo = [director:Director, manager:Manager] 5 | } 6 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/init/BootStrap.groovy: -------------------------------------------------------------------------------- 1 | class BootStrap { 2 | 3 | def init = { servletContext -> 4 | } 5 | def destroy = { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/bookStore/grails-app/init/bookstore/Application.groovy: -------------------------------------------------------------------------------- 1 | package bookstore 2 | 3 | import grails.boot.GrailsApp 4 | import grails.boot.config.GrailsAutoConfiguration 5 | 6 | class Application extends GrailsAutoConfiguration { 7 | static void main(String[] args) { 8 | GrailsApp.run(Application, args) 9 | } 10 | } -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/base/AbstractTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import abstractclass.AbstractClass 4 | import abstractclass.AnotherConcreteSubClass 5 | import abstractclass.ConcreteSubClass 6 | import abstractclass.RelatedToAbstract 7 | import abstractclass.AbstractSubClass 8 | import grails.buildtestdata.TestDataBuilder 9 | import grails.testing.mixin.integration.Integration 10 | import grails.gorm.transactions.Rollback 11 | import spock.lang.Specification 12 | 13 | @Integration 14 | @Rollback 15 | class AbstractTests extends Specification implements TestDataBuilder { 16 | 17 | void testSuccessfulBuildOfDomainAbstractClass() { 18 | when: 19 | def abstractClass = build(AbstractClass) 20 | then: 21 | abstractClass 22 | abstractClass.ident() > 0 23 | abstractClass instanceof ConcreteSubClass 24 | 25 | when: 26 | def abstractSubClass = build(AbstractSubClass) 27 | then: 28 | abstractSubClass 29 | abstractSubClass.ident() > 0 30 | 31 | // This could be either one of these 32 | abstractSubClass instanceof AnotherConcreteSubClass || abstractSubClass instanceof ConcreteSubClass 33 | 34 | when: 35 | def concreteClass = build(ConcreteSubClass) 36 | 37 | then: 38 | concreteClass 39 | concreteClass.ident() > 0 40 | } 41 | 42 | void testSuccessfulBuildOfRelatedAbstractClass() { 43 | when: 44 | def related = build(RelatedToAbstract) 45 | 46 | then: 47 | related 48 | related.ident() > 0 49 | related.genericParent 50 | related.genericParent.ident() > 0 51 | related.genericParent instanceof ConcreteSubClass 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/base/AssignedKeyTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import grails.buildtestdata.TestDataBuilder 4 | import grails.testing.mixin.integration.Integration 5 | import grails.gorm.transactions.Rollback 6 | import spock.lang.Ignore 7 | import spock.lang.Specification 8 | import standalone.AssignedKey 9 | import standalone.ChildWithAssignedKey 10 | 11 | @Integration 12 | @Rollback 13 | class AssignedKeyTests extends Specification implements TestDataBuilder { 14 | void testBuildWithKey() { 15 | when: 16 | def obj = build(AssignedKey, [id: "FOO"]) 17 | 18 | then: 19 | obj != null 20 | obj.attribute == "attribute" 21 | 22 | def o2 = AssignedKey.get("FOO") 23 | o2 != null 24 | } 25 | 26 | void testBuildWithoutKey() { 27 | when: 28 | def obj = build(AssignedKey) 29 | 30 | then: 31 | obj != null 32 | obj.attribute == "attribute" 33 | 34 | and: "key is assigned using blank handler" 35 | obj.id == 'x' 36 | 37 | def o2 = AssignedKey.get(obj.id) 38 | o2 != null 39 | } 40 | 41 | void testBuildChildWithAssignedKeyInParent() { 42 | when: 43 | def obj = build(ChildWithAssignedKey, [id: 'FOO']) 44 | 45 | then: 46 | obj != null 47 | obj.attribute == "attribute" 48 | 49 | def o2 = ChildWithAssignedKey.get('FOO') 50 | o2 != null 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/base/BuildLazyTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import bookstore.Author 4 | import grails.buildtestdata.TestDataBuilder 5 | import grails.testing.mixin.integration.Integration 6 | import grails.gorm.transactions.Rollback 7 | import spock.lang.Specification 8 | 9 | @Rollback 10 | @Integration 11 | class BuildLazyTests extends Specification implements TestDataBuilder { 12 | 13 | void testBuildLazyNoParamsCreatesNewWhenNoneExist() { 14 | assert Author.count() == 0 15 | 16 | when: 17 | def domainObject = findOrBuild(Author) 18 | 19 | then: 20 | domainObject != null 21 | Author.count() == 1 22 | } 23 | 24 | void testBuildLazyNoParamsFindsExistingWithoutCreateNew() { 25 | build(Author, [firstName: "Foo", lastName: "Qux"]) 26 | assert Author.count() == 1 27 | 28 | when: 29 | def domainObject = build(Author, find: true) 30 | 31 | then: 32 | domainObject 33 | domainObject.firstName == "Foo" 34 | domainObject.lastName == "Qux" 35 | Author.count() == 1 36 | } 37 | void "Test with new findOrBuild"() { 38 | assert Author.count() == 0 39 | 40 | when: 41 | def domainObject = Author.findOrBuild(firstName: "Bar") 42 | 43 | then: 44 | domainObject 45 | Author.count() == 1 46 | } 47 | 48 | void "Test build find with legacy buildLazy method"() { 49 | assert Author.count() == 0 50 | 51 | when: 52 | def domainObject = Author.findOrBuild(firstName: "Bar") 53 | 54 | then: 55 | domainObject 56 | Author.count() == 1 57 | } 58 | 59 | void "Test build lazy with specific id property given"() { 60 | def auth1 = build(Author, [firstName: "Foo", lastName: "Qux"]) 61 | build(Author, [firstName: "Bar", lastName: "Baz"]) 62 | 63 | when: 64 | def domainObject = Author.findOrBuild(id: auth1.id) 65 | 66 | then: 67 | domainObject 68 | domainObject.id == auth1.id 69 | } 70 | 71 | void testBuildLazyWithParamsCreatesNewWhenNoMatchingExist() { 72 | Author.build(firstName: "Foo") 73 | assert Author.count() == 1 74 | 75 | when: 76 | def domainObject = Author.findOrBuild(firstName: "Bar") 77 | 78 | then: 79 | domainObject 80 | Author.count() == 2 81 | } 82 | 83 | void testBuildLazyWithParamsFindsExistingWithoutCreateNew() { 84 | Author.build(firstName: "Foo", lastName: "Qux") 85 | assert Author.count() == 1 86 | 87 | when: 88 | def domainObject = Author.findOrBuild(firstName: "Foo") 89 | 90 | then: 91 | domainObject 92 | domainObject.firstName == "Foo" 93 | domainObject.lastName == "Qux" 94 | Author.count() == 1 95 | } 96 | 97 | void testBuildLazyWithParamsCreatesNewWithOnlyPartialMatch() { 98 | Author.build(firstName: "Foo", lastName: "Qux") 99 | assert Author.count() == 1 100 | 101 | when: 102 | def domainObject = Author.findOrBuild(firstName: "Foo", lastName: "Bar") 103 | 104 | then: 105 | domainObject 106 | domainObject.firstName == "Foo" 107 | domainObject.lastName == "Bar" 108 | Author.count() == 2 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/base/ConfigWithoutResetTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import config.Article 4 | import grails.buildtestdata.TestDataBuilder 5 | import grails.buildtestdata.TestDataConfigurationHolder 6 | import grails.testing.mixin.integration.Integration 7 | import grails.gorm.transactions.Rollback 8 | import spock.lang.Specification 9 | 10 | @Rollback 11 | @Integration 12 | class ConfigWithoutResetTests extends Specification implements TestDataBuilder { 13 | // test order isn't guaranteed with Java 7 but this way, with 2 tests, we know that reset is working because 14 | // one of the tests will run first, then the other, but both get the same articles 15 | void testBuildFirstUniqueArticle() { 16 | resetAndBuildUniqueArticles() 17 | } 18 | 19 | void testBuildSecondUniqueArticle() { 20 | resetAndBuildUniqueArticles() 21 | } 22 | 23 | private static resetAndBuildUniqueArticles() { 24 | TestDataConfigurationHolder.reset() // reset to a known state for these tests 25 | def a = Article.build() 26 | assert a.name == "Article 1" 27 | 28 | def b = Article.build() 29 | assert b.name == "Article 2" 30 | } 31 | 32 | void cleanupSpec() { 33 | TestDataConfigurationHolder.reset() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/base/DoWithTestDataConfigTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import config.Hotel 4 | import grails.buildtestdata.TestDataBuilder 5 | import grails.buildtestdata.TestDataConfigurationHolder 6 | import grails.gorm.transactions.Rollback 7 | import grails.testing.mixin.integration.Integration 8 | import spock.lang.Specification 9 | 10 | @Rollback 11 | @Integration 12 | class DoWithTestDataConfigTests extends Specification implements TestDataBuilder { 13 | 14 | @Override 15 | Closure doWithTestDataConfig() {{-> 16 | testDataConfig { 17 | sampleData { 18 | 'config.Hotel' { 19 | name = {-> "Westin" } 20 | } 21 | } 22 | } 23 | }} 24 | 25 | void "Resetting uses default configuration"() { 26 | given: 27 | TestDataConfigurationHolder.reset() 28 | 29 | when: 30 | Hotel testHotel = build(Hotel) 31 | 32 | then: 33 | testHotel.name == "Motel 6" 34 | } 35 | 36 | void "doWithTestDataConfig overrides values in the default configuration"() { 37 | when: 38 | Hotel testHotel = build(Hotel) 39 | 40 | then: 41 | testHotel.name == "Westin" 42 | } 43 | 44 | void cleanupSpec() { 45 | TestDataConfigurationHolder.reset() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/base/EnumTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import enumtest.Car 4 | import enumtest.CarStatus 5 | import enumtest.Door 6 | import enumtest.DoorStatus 7 | import grails.buildtestdata.TestDataBuilder 8 | import grails.testing.mixin.integration.Integration 9 | import grails.gorm.transactions.Rollback 10 | import spock.lang.Specification 11 | 12 | @Rollback 13 | @Integration 14 | class EnumTests extends Specification implements TestDataBuilder { 15 | void testCarStatusEnumPopulated() { 16 | when: 17 | Car car = build(Car) 18 | then: 19 | car 20 | car.status == CarStatus.REVERSE 21 | } 22 | 23 | void testDoorStatusEnumPopulated() { 24 | when: 25 | Door door = build(Door) 26 | then: 27 | door 28 | door.status == DoorStatus.OPEN 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/base/LocalScopedConfigTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import grails.buildtestdata.TestDataBuilder 4 | import grails.testing.mixin.integration.Integration 5 | import grails.gorm.transactions.Rollback 6 | import magazine.Issue 7 | import magazine.Page 8 | import spock.lang.Specification 9 | 10 | @Rollback 11 | @Integration 12 | class LocalScopedConfigTests extends Specification implements TestDataBuilder { 13 | void testAddPagesToIssue() { 14 | when: 15 | def issue = build(Issue) 16 | issue.pages = (1..5).collect { build(Page, [issue: issue, number: it]) }.toSet() as SortedSet 17 | 18 | then: 19 | issue.pages.size() == 5 20 | Issue.list().size() // don't build extra issues when building pages == 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/base/MinSizeTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import bookstore.Address 4 | import grails.buildtestdata.TestDataBuilder 5 | import grails.testing.mixin.integration.Integration 6 | import grails.gorm.transactions.Rollback 7 | import spock.lang.Specification 8 | 9 | @Rollback 10 | @Integration 11 | class MinSizeTests extends Specification implements TestDataBuilder { 12 | void testEmailMinSize() { 13 | when: 14 | def domainObject = build(Address) 15 | 16 | then: 17 | domainObject 18 | domainObject.id 19 | domainObject.emailAddress 20 | domainObject.emailAddress.size() == 40 21 | } 22 | 23 | void testUrlMinSize() { 24 | when: 25 | def domainObject = build(Address) 26 | 27 | then: 28 | domainObject 29 | domainObject.id 30 | domainObject.webSite 31 | domainObject.webSite.size() == 40 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/base/RelationTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import bookstore.Author 4 | import bookstore.Book 5 | import bookstore.Invoice 6 | import grails.buildtestdata.TestDataBuilder 7 | import grails.testing.mixin.integration.Integration 8 | import grails.gorm.transactions.Rollback 9 | import human.Arm 10 | import human.Face 11 | import magazine.Issue 12 | import magazine.Page 13 | import org.grails.core.DefaultGrailsDomainClass 14 | import spock.lang.Specification 15 | 16 | @Rollback 17 | @Integration 18 | class RelationTests extends Specification implements TestDataBuilder { 19 | void testOneToOneCascades() { 20 | when: 21 | def domainObject = build(Face) 22 | 23 | then: 24 | domainObject 25 | domainObject.id 26 | domainObject.nose 27 | domainObject.nose.id 28 | } 29 | 30 | void testBelongsToGetsHasMany() { 31 | when: 32 | def domainObject = build(Book) 33 | 34 | then: 35 | domainObject 36 | domainObject.author 37 | domainObject.author.books 38 | domainObject.author.books.find { book -> book == domainObject } 39 | domainObject.id 40 | } 41 | 42 | void testHasManyNullableFalse() { 43 | when: 44 | def domainObject = build(Author) 45 | 46 | then: 47 | domainObject 48 | domainObject.id 49 | domainObject.books 50 | domainObject.address 51 | 2 == domainObject.books.size() 52 | domainObject.books.each { book -> assert domainObject == book.author } 53 | } 54 | 55 | void testManyToManyNullableFalse() { 56 | when: 57 | def domainObject = build(Invoice) 58 | then: 59 | domainObject 60 | domainObject.id 61 | domainObject.books 62 | 3 == domainObject.books.size() 63 | } 64 | 65 | void testParentCollectionUpdatedWhenChildAutomaticallyAdded() { 66 | when: 67 | def page = build(Page) 68 | 69 | then: 70 | page 71 | page.issue 72 | [page.id] == page.issue.pages.id 73 | } 74 | 75 | void testParentCollectionUpdatedWhenChildManuallyAdded() { 76 | def issue = new Issue(title: "title").save(failOnError: true) 77 | assert issue 78 | 79 | when: 80 | def page = build(Page, [issue: issue]) 81 | 82 | then: 83 | page 84 | page.issue == issue 85 | [page.id] == issue.pages.id 86 | } 87 | 88 | void testHasOne() { 89 | when: 90 | def arm = build(Arm) 91 | 92 | then: 93 | def hand = arm.hand 94 | assert arm 95 | assert hand 96 | assert hand.arm == arm 97 | assert arm.hand == hand 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/base/StandaloneTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import grails.buildtestdata.TestDataBuilder 4 | import grails.testing.mixin.integration.Integration 5 | import grails.gorm.transactions.Rollback 6 | import spock.lang.Specification 7 | import standalone.Standalone 8 | 9 | @Rollback 10 | @Integration 11 | class StandaloneTests extends Specification implements TestDataBuilder { 12 | void testNullableBelongsToNotFollowed() { 13 | when: 14 | def standalone = build(Standalone) // standalone.Standalone has a "parent" property on it that is nullable (otherwise it'd get in an infinite loop) 15 | 16 | then: 17 | standalone 18 | standalone.id 19 | !standalone.parent 20 | standalone.emailAddress != null 21 | } 22 | 23 | void testBuildStandalonePassAllVariables() { 24 | def created = new Date() 25 | 26 | when: 27 | def obj = build(Standalone, [name: "Foo", age: 14, created: created, emailAddress: "foo@bar.com"]) 28 | 29 | then: 30 | assertValidDomainObject(obj) 31 | 32 | obj.name == "Foo" 33 | obj.age == 14 34 | obj.created == created 35 | "foo@bar.com" == obj.emailAddress 36 | } 37 | 38 | void testBuildStandalonePassNoVariables() { 39 | when: 40 | def obj = build(Standalone) 41 | 42 | then: 43 | assertValidDomainObject(obj) 44 | obj.name // by default it just uses the property name for the value for strings == "name" 45 | obj.created 46 | obj.age == 0 47 | } 48 | 49 | void testUniqueEmailAddressWorksFirstThenFails() { 50 | when: 51 | def first = build(Standalone) 52 | 53 | then: 54 | first.emailAddress != null 55 | first.emailAddress == "a@b.com" 56 | 57 | // To do this right you'd need to override in the TestDataConfig.groovy file with a custom closure 58 | when: 59 | build(Standalone) 60 | 61 | then: 62 | thrown(RuntimeException) 63 | } 64 | 65 | void assertValidDomainObject(domainObject) { 66 | assert domainObject 67 | assert domainObject.id 68 | assert domainObject.errors.errorCount == 0 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/base/StandaloneTestsWithBuildTrait.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import grails.buildtestdata.mixin.Build 4 | import grails.gorm.transactions.Rollback 5 | import grails.testing.mixin.integration.Integration 6 | import spock.lang.Specification 7 | import standalone.Standalone 8 | 9 | @Rollback 10 | @Integration 11 | @Build([Standalone]) 12 | class StandaloneTestsWithBuildTrait extends Specification { 13 | void testNullableBelongsToNotFollowed() { 14 | when: 15 | def standalone = Standalone.build() // standalone.Standalone has a "parent" property on it that is nullable (otherwise it'd get in an infinite loop) 16 | 17 | then: 18 | standalone 19 | standalone.id 20 | !standalone.parent 21 | standalone.emailAddress != null 22 | } 23 | 24 | void testBuildStandalonePassAllVariables() { 25 | def created = new Date() 26 | 27 | when: 28 | def obj = Standalone.build(name: "Foo", age: 14, created: created, emailAddress: "foo@bar.com") 29 | 30 | then: 31 | assertValidDomainObject(obj) 32 | 33 | obj.name == "Foo" 34 | obj.age == 14 35 | obj.created == created 36 | "foo@bar.com" == obj.emailAddress 37 | } 38 | 39 | void testBuildStandalonePassNoVariables() { 40 | when: 41 | def obj = Standalone.build() 42 | 43 | then: 44 | assertValidDomainObject(obj) 45 | obj.name // by default it just uses the property name for the value for strings == "name" 46 | obj.created 47 | obj.age == 0 48 | } 49 | 50 | void testUniqueEmailAddressWorksFirstThenFails() { 51 | when: 52 | def first = Standalone.build() 53 | 54 | then: 55 | first.emailAddress != null 56 | first.emailAddress == "a@b.com" 57 | 58 | // To do this right you'd need to override in the TestDataConfig.groovy file with a custom closure 59 | when: 60 | Standalone.build() 61 | 62 | then: 63 | thrown(RuntimeException) 64 | } 65 | 66 | void assertValidDomainObject(domainObject) { 67 | assert domainObject 68 | assert domainObject.id 69 | assert domainObject.errors.errorCount == 0 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/base/SubclassTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import grails.buildtestdata.TestDataBuilder 4 | import grails.testing.mixin.integration.Integration 5 | import grails.gorm.transactions.Rollback 6 | import spock.lang.Specification 7 | import subclassing.RelatedClass 8 | import subclassing.SubClass 9 | import subclassing.SuperClass 10 | 11 | @Rollback 12 | @Integration 13 | class SubclassTests extends Specification implements TestDataBuilder { 14 | void testSuccessfulBuildOfDomainSubclass() { 15 | when: 16 | def subClass = build(SubClass) 17 | 18 | then: 19 | subClass 20 | subClass.ident() > 0 21 | subClass.relatedClass.superClassInstances.contains(subClass) 22 | 23 | when: 24 | def relatedClass = build(RelatedClass) 25 | 26 | then: 27 | relatedClass 28 | relatedClass.ident() > 0 29 | 30 | when: 31 | def superClass = build(SuperClass) 32 | 33 | then: 34 | superClass 35 | superClass.ident() > 0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/embedded/EmbeddingTests.groovy: -------------------------------------------------------------------------------- 1 | package embedded 2 | 3 | import grails.buildtestdata.TestDataBuilder 4 | import grails.buildtestdata.TestDataConfigurationHolder 5 | import grails.testing.mixin.integration.Integration 6 | import grails.gorm.transactions.Rollback 7 | import spock.lang.Specification 8 | 9 | @Rollback 10 | @Integration 11 | class EmbeddingTests extends Specification implements TestDataBuilder { 12 | void "default embedded value"() { 13 | TestDataConfigurationHolder.sampleData = [:] 14 | 15 | when: 16 | Embedding e = build(Embedding) 17 | 18 | then: 19 | e.inner.someValue == 'someValue' 20 | } 21 | 22 | void "embedded value loaded from TestDataConfig"() { 23 | TestDataConfigurationHolder.reset() 24 | 25 | when: 26 | Embedding e = build(Embedding) 27 | 28 | then: 29 | e.inner.someValue == 'value' 30 | } 31 | 32 | void "embedded value overriden using the nested map syntax"() { 33 | when: 34 | Embedding e = build(Embedding, [inner: [someValue: 'test']]) 35 | 36 | then: 37 | e.inner.someValue == 'test' 38 | } 39 | 40 | void "embedded value overriden via object parameter"() { 41 | when: 42 | Embedding e = build(Embedding, [inner: new Embedded(someValue: 'test')]) 43 | 44 | then: 45 | e.inner.someValue == 'test' 46 | } 47 | 48 | void cleanupSpec() { 49 | TestDataConfigurationHolder.reset() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/list/EstablishedAuthorTests.groovy: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import bookstore.EstablishedAuthor 4 | import grails.buildtestdata.TestDataBuilder 5 | import grails.testing.mixin.integration.Integration 6 | import grails.gorm.transactions.Rollback 7 | import spock.lang.Specification 8 | 9 | @Rollback 10 | @Integration 11 | class EstablishedAuthorTests extends Specification implements TestDataBuilder { 12 | void testRequiredListAndSetOk() { 13 | when: 14 | EstablishedAuthor establishedAuthor = build(EstablishedAuthor, [name: "Steven King"]) 15 | then: 16 | establishedAuthor.hardcoverBooks != null 17 | establishedAuthor.hardcoverBooks.size() == 1 18 | establishedAuthor.paperbackBooks != null 19 | establishedAuthor.paperbackBooks.size() == 1 20 | establishedAuthor.id > 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/list/SantaTests.groovy: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import grails.buildtestdata.TestDataBuilder 4 | import grails.testing.mixin.integration.Integration 5 | import grails.gorm.transactions.Rollback 6 | import spock.lang.Specification 7 | 8 | @Rollback 9 | @Integration 10 | class SantaTests extends Specification implements TestDataBuilder { 11 | void testChildListOk() { 12 | when: 13 | def santa = build(Santa, [firstName: 'Santa', lastName: 'Claus']) 14 | then: 15 | santa.children == null 16 | 17 | when: 18 | build(Child, [name: 'ivan', grade: 2, dateOfBirth: new Date(), santa: santa]) 19 | then: 20 | santa.children.size() == 1 21 | 22 | when: 23 | build(Child, [name: 'hazel', grade: 0, dateOfBirth: new Date(), santa: santa]) 24 | then: 25 | santa.children.size() == 2 26 | 27 | when: 28 | santa.save(flush: true) 29 | 30 | then: 31 | santa.children.size() == 2 32 | santa.id > 0 33 | } 34 | 35 | void testElvesListMinConstraintOk() { 36 | when: 37 | def santa = build(Santa, [firstName: 'Santa', lastName: 'Claus']) 38 | then: 39 | santa.children == null 40 | santa.elves != null 41 | santa.elves.size() == 1 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/bookStore/src/integration-test/groovy/triangle/TriangleRelationshipTests.groovy: -------------------------------------------------------------------------------- 1 | package triangle 2 | 3 | import grails.buildtestdata.TestDataBuilder 4 | import grails.testing.mixin.integration.Integration 5 | import grails.gorm.transactions.Rollback 6 | import spock.lang.Specification 7 | 8 | @Rollback 9 | @Integration 10 | class TriangleRelationshipTests extends Specification implements TestDataBuilder { 11 | void testBuildTriangleRelationship() { 12 | // Director has many managers and workers 13 | // manager belongsto director and has many workers 14 | // workers belongs to a director and a manager, we should be able to build any one of these successfully 15 | expect: 16 | build(Worker) 17 | build(Manager) 18 | build(Director) 19 | } 20 | 21 | void testBuildTriangleRelationshipPartiallyCompleteAlready() { 22 | when: 23 | def manager = build(Manager) 24 | 25 | then: 26 | manager 27 | 28 | when: 29 | def director = build(Director, [managers: [manager]]) 30 | then: 31 | director 32 | build(Worker, [manager: manager]) 33 | build(Worker, [director: director]) 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/bookStore/src/main/groovy/embedded/Embedded.groovy: -------------------------------------------------------------------------------- 1 | package embedded 2 | 3 | import grails.validation.Validateable 4 | 5 | class Embedded implements Validateable { 6 | String someValue 7 | 8 | static constraints = { 9 | someValue nullable: false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/bookStore/src/main/groovy/embedded/LatLon.groovy: -------------------------------------------------------------------------------- 1 | package embedded 2 | 3 | class LatLon { 4 | BigDecimal longitude 5 | BigDecimal latitude 6 | 7 | static constraints = { 8 | longitude(min: -180.0, max: 180.0, scale: 4) 9 | latitude(min: -90.0, max: 90.0, scale: 4) 10 | } 11 | 12 | String toString() { "$longitude, $latitude" } 13 | } -------------------------------------------------------------------------------- /examples/bookStore/src/main/groovy/enums/BookType.groovy: -------------------------------------------------------------------------------- 1 | package enums 2 | 3 | enum BookType { 4 | Fiction, NonFiction 5 | } -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/AbstractDefaultUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import abstractclass.AbstractClass 4 | import abstractclass.AbstractSubClass 5 | 6 | import abstractclass.AnotherConcreteSubClass 7 | import abstractclass.ConcreteSubClass 8 | import grails.buildtestdata.TestData 9 | import grails.buildtestdata.TestDataConfigurationHolder 10 | import grails.buildtestdata.BuildDataTest 11 | 12 | import spock.lang.Specification 13 | 14 | class AbstractDefaultUnitTests extends Specification implements BuildDataTest { 15 | void setup() { 16 | TestDataConfigurationHolder.reset() 17 | 18 | // Since we are changing out the subclass defaults, prevent any caching 19 | TestData.clear() 20 | } 21 | 22 | void testBuildWithNoDefault() { 23 | TestDataConfigurationHolder.config.abstractDefault = [:] 24 | 25 | when: 26 | build(AbstractClass) 27 | 28 | then: "this is no longer supported in BTD 3.3 and later, specific default required" 29 | thrown(RuntimeException) 30 | } 31 | 32 | void testSuccessfulBuildWithDefault() { 33 | TestDataConfigurationHolder.config.abstractDefault = ['abstractclass.AbstractSubClass': ConcreteSubClass] 34 | mockDomain(AbstractSubClass) 35 | 36 | when: 37 | def obj = build(AbstractSubClass) 38 | 39 | then: 40 | obj instanceof ConcreteSubClass 41 | } 42 | 43 | void testSuccessfulBuildWithDifferentDefault() { 44 | TestDataConfigurationHolder.config.abstractDefault = ['abstractclass.AbstractSubClass': AnotherConcreteSubClass] 45 | mockDomain(AbstractSubClass) 46 | 47 | when: 48 | def obj = build(AbstractSubClass) 49 | 50 | then: 51 | obj instanceof AnotherConcreteSubClass 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/AbstractRelatedUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import abstractclass.ConcreteSubClass 4 | import abstractclass.RelatedToAbstract 5 | import grails.buildtestdata.BuildDataTest 6 | import grails.buildtestdata.TestDataConfigurationHolder 7 | import spock.lang.Specification 8 | 9 | class AbstractRelatedUnitTests extends Specification implements BuildDataTest { 10 | void setup() { 11 | TestDataConfigurationHolder.reset() 12 | } 13 | 14 | void testSuccessfulBuild() { 15 | mockDomains(RelatedToAbstract, ConcreteSubClass) 16 | 17 | when: 18 | def theClass = build(RelatedToAbstract) 19 | 20 | then: 21 | assert theClass != null 22 | assert theClass.ident() > 0 23 | assert theClass.genericParent != null 24 | assert theClass.genericParent instanceof ConcreteSubClass 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/BuildLazyUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import bookstore.Author 4 | import grails.buildtestdata.BuildDataTest 5 | import spock.lang.Specification 6 | 7 | class BuildLazyUnitTests extends Specification implements BuildDataTest { 8 | 9 | void setupSpec() { 10 | mockDomains(Author) 11 | } 12 | 13 | void testBuildLazyNoParamsCreatesNewWhenNoneExist() { 14 | assert Author.count() == 0 15 | 16 | when: 17 | def domainObject = findOrBuild(Author) 18 | 19 | then: 20 | assert domainObject 21 | assert Author.count() == 1 22 | } 23 | 24 | void testBuildLazyNoParamsFindsExistingWithoutCreateNew() { 25 | build(Author, [firstName: "Foo", lastName: "Qux"]) 26 | assert Author.count() == 1 27 | 28 | when: 29 | def domainObject = findOrBuild(Author) 30 | 31 | then: 32 | assert domainObject 33 | assert domainObject.firstName == "Foo" 34 | assert domainObject.lastName == "Qux" 35 | assert Author.count() == 1 36 | } 37 | 38 | void testBuildLazyWithParamsCreatesNewWhenNoneExist() { 39 | assert Author.count() == 0 40 | 41 | when: 42 | def domainObject = findOrBuild(Author, [firstName: "Bar"]) 43 | 44 | then: 45 | assert domainObject 46 | assert Author.count() == 1 47 | } 48 | 49 | void testBuildLazyWithParamsCreatesNewWhenNoMatchingExist() { 50 | build(Author, [firstName: "Foo"]) 51 | assert Author.count() == 1 52 | 53 | when: 54 | def domainObject = findOrBuild(Author, [firstName: "Bar"]) 55 | 56 | then: 57 | assert domainObject 58 | assert Author.count() == 2 59 | } 60 | 61 | void testBuildLazyWithParamsFindsExistingWithoutCreateNew() { 62 | build(Author, [firstName: "Foo", lastName: "Qux"]) 63 | assert Author.count() == 1 64 | 65 | when: 66 | def domainObject = findOrBuild(Author, [firstName: "Foo"]) 67 | 68 | then: 69 | assert domainObject 70 | assert domainObject.firstName == "Foo" 71 | assert domainObject.lastName == "Qux" 72 | assert Author.count() == 1 73 | } 74 | 75 | void testBuildLazyWithParamsCreatesNewWithOnlyPartialMatch() { 76 | build(Author, [firstName: "Foo", lastName: "Qux"]) 77 | assert Author.count() == 1 78 | 79 | when: 80 | def domainObject = findOrBuild(Author, [firstName: "Foo", lastName: "Bar"]) 81 | 82 | then: 83 | assert domainObject 84 | assert domainObject.firstName == "Foo" 85 | assert domainObject.lastName == "Bar" 86 | assert Author.count() == 2 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/ConfigWithoutResetUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import config.Article 4 | import grails.buildtestdata.TestDataConfigurationHolder 5 | import grails.buildtestdata.BuildDataTest 6 | import spock.lang.Specification 7 | 8 | class ConfigWithoutResetUnitTests extends Specification implements BuildDataTest { 9 | void setup() { 10 | mockDomain(Article) 11 | } 12 | 13 | // the TestDataConfig file sets the name values for Article's unique name property 14 | // it has a variable in the config that increments if we don't reset() the config 15 | void testBuildFirstUniqueArticle() { 16 | TestDataConfigurationHolder.reset() // reset to a known state for these tests 17 | 18 | when: 19 | def a = build(Article) 20 | 21 | then: 22 | assert "Article 1" == a.name 23 | } 24 | 25 | void testBuildSecondUniqueArticle() { 26 | TestDataConfigurationHolder.reset() 27 | 28 | when: 29 | def a1 = build(Article) 30 | def a2 = build(Article) 31 | 32 | then: 33 | assert "Article 2" == a2.name 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/EnumUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import enumtest.Car 4 | import enumtest.CarStatus 5 | import enumtest.Door 6 | import enumtest.DoorStatus 7 | import grails.buildtestdata.BuildDataTest 8 | import spock.lang.Specification 9 | 10 | class EnumUnitTests extends Specification implements BuildDataTest { 11 | void setupSpec() { 12 | mockDomains(Car, Door) 13 | } 14 | 15 | void testCarStatusEnumPopulated() { 16 | when: 17 | Car car = build(Car) 18 | 19 | then: 20 | assert car 21 | assert CarStatus.REVERSE == car.status 22 | } 23 | 24 | void testDoorStatusEnumPopulated() { 25 | when: 26 | Door door = build(Door) 27 | 28 | then: 29 | assert door 30 | assert DoorStatus.OPEN == door.status 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/LocalScopedConfigUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import grails.buildtestdata.BuildDataTest 4 | import magazine.Issue 5 | import magazine.Page 6 | import spock.lang.Specification 7 | 8 | class LocalScopedConfigUnitTests extends Specification implements BuildDataTest { 9 | void setupSpec() { 10 | mockDomains(Issue, Page) 11 | } 12 | 13 | void testAddPagesToIssue() { 14 | when: 15 | def issue = build(Issue) 16 | issue.pages = (1..5).collect { build(Page, [issue: issue, number: it]) }.toSet() as SortedSet 17 | 18 | then: 19 | assert 5 == issue.pages.size() 20 | assert 1 == Issue.list().size() // don't build extra issues when building pages 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/MatchesUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import bookstore.Invoice 4 | import grails.buildtestdata.BuildDataTest 5 | import spock.lang.Specification 6 | 7 | class MatchesUnitTests extends Specification implements BuildDataTest { 8 | void setupSpec() { 9 | mockDomains(Invoice) 10 | } 11 | 12 | void testBuildNoArgs() { 13 | when: 14 | def invoice = build(Invoice) 15 | 16 | then: 17 | assert invoice != null 18 | assert invoice.departmentCode.matches("[A-Z]{2}[0-9]{4}") 19 | } 20 | 21 | void testBuildWithMatching() { 22 | when: 23 | def invoice = build(Invoice, [departmentCode: "AA1234"]) 24 | 25 | then: 26 | assert invoice != null 27 | assert invoice.departmentCode == "AA1234" 28 | } 29 | 30 | void testBuildWithNonMatchingArg() { 31 | when: 32 | build(Invoice, [departmentCode: "Hmmmm?"]) 33 | 34 | then: 35 | thrown(RuntimeException) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/MinSizeUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import bookstore.Address 4 | import grails.buildtestdata.BuildDataTest 5 | import spock.lang.Specification 6 | 7 | class MinSizeUnitTests extends Specification implements BuildDataTest { 8 | void setupSpec() { 9 | mockDomains(Address) 10 | } 11 | 12 | void testEmailMinSize() { 13 | when: 14 | def domainObject = build(Address) 15 | 16 | then: 17 | assert domainObject 18 | assert domainObject.id 19 | assert domainObject.emailAddress 20 | assert domainObject.emailAddress.size() == 40 21 | } 22 | 23 | void testUrlMinSize() { 24 | when: 25 | def domainObject = build(Address) 26 | 27 | then: 28 | assert domainObject 29 | assert domainObject.id 30 | assert domainObject.webSite 31 | assert domainObject.webSite.size() == 40 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/RelationUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import bookstore.Author 4 | import bookstore.Book 5 | import bookstore.Invoice 6 | import grails.buildtestdata.BuildDataTest 7 | import human.Arm 8 | import human.Face 9 | import magazine.Issue 10 | import magazine.Page 11 | 12 | import spock.lang.Specification 13 | 14 | class RelationUnitTests extends Specification implements BuildDataTest { 15 | void setupSpec() { 16 | mockDomains(Face, Book, Author, Invoice, Page, Issue, Arm) 17 | } 18 | 19 | void testOneToOneCascades() { 20 | when: 21 | def domainObject = build(Face) 22 | 23 | then: 24 | assert domainObject 25 | assert domainObject.id 26 | assert domainObject.nose 27 | assert domainObject.nose.id 28 | } 29 | 30 | void testBelongsToGetsHasMany() { 31 | when: 32 | def domainObject = build(Book) 33 | 34 | then: 35 | assert domainObject 36 | assert domainObject.author 37 | assert domainObject.author.books 38 | assert domainObject.author.books.find { book -> 39 | book == domainObject 40 | } 41 | assert domainObject.id 42 | } 43 | 44 | void testHasManyNullableFalse() { 45 | when: 46 | def domainObject = build(Author) 47 | 48 | then: 49 | assert domainObject 50 | assert domainObject.id 51 | assert domainObject.books 52 | assert domainObject.address 53 | assert 2 == domainObject.books.size() 54 | domainObject.books.each { book -> 55 | assert domainObject == book.author 56 | } 57 | } 58 | 59 | void testManyToManyNullableFalse() { 60 | when: 61 | def domainObject = build(Invoice) 62 | 63 | then: 64 | assert domainObject 65 | assert domainObject.id 66 | assert domainObject.books 67 | assert 3 == domainObject.books.size() 68 | } 69 | 70 | void testParentCollectionUpdatedWhenChildAutomaticallyAdded() { 71 | when: 72 | def page = build(Page) 73 | 74 | then: 75 | assert page 76 | assert page.issue 77 | assert [page.id] == page.issue.pages.id 78 | } 79 | 80 | void testParentCollectionUpdatedWhenChildManuallyAdded() { 81 | def issue = new Issue(title: "title").save(failOnError: true) 82 | assert issue 83 | 84 | when: 85 | def page = build(Page, [issue: issue]) 86 | 87 | then: 88 | assert page 89 | assert page.issue == issue 90 | assert [page.id] == issue.pages.id 91 | } 92 | 93 | void testHasOne() { 94 | when: 95 | def arm = build(Arm) 96 | def hand = arm.hand 97 | 98 | then: 99 | assert arm 100 | assert hand 101 | assert hand.arm == arm 102 | assert arm.hand == hand 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/RelationUnitTestsUsingBuild.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import bookstore.Author 4 | import bookstore.Book 5 | import bookstore.Invoice 6 | import grails.buildtestdata.BuildDataTest 7 | import grails.buildtestdata.mixin.Build 8 | import human.Arm 9 | import human.Face 10 | import magazine.Issue 11 | import magazine.Page 12 | import spock.lang.Specification 13 | 14 | @Build([Arm, Face, Book, Author, Invoice, Page]) 15 | class RelationUnitTestsUsingBuild extends Specification implements BuildDataTest { 16 | void testOneToOneCascades() { 17 | when: 18 | def domainObject = Face.build() 19 | 20 | then: 21 | assert domainObject 22 | assert domainObject.id 23 | assert domainObject.nose 24 | assert domainObject.nose.id 25 | } 26 | 27 | void testBelongsToGetsHasMany() { 28 | when: 29 | def domainObject = Book.build() 30 | 31 | then: 32 | assert domainObject 33 | assert domainObject.author 34 | assert domainObject.author.books 35 | assert domainObject.author.books.find { book -> 36 | book == domainObject 37 | } 38 | assert domainObject.id 39 | } 40 | 41 | void testHasManyNullableFalse() { 42 | when: 43 | def domainObject = Author.build() 44 | 45 | then: 46 | assert domainObject 47 | assert domainObject.id 48 | assert domainObject.books 49 | assert domainObject.address 50 | assert 2 == domainObject.books.size() 51 | domainObject.books.each { book -> 52 | assert domainObject == book.author 53 | } 54 | } 55 | 56 | void testManyToManyNullableFalse() { 57 | when: 58 | def domainObject = Invoice.build() 59 | 60 | then: 61 | assert domainObject 62 | assert domainObject.id 63 | assert domainObject.books 64 | assert 3 == domainObject.books.size() 65 | } 66 | 67 | void testParentCollectionUpdatedWhenChildAutomaticallyAdded() { 68 | when: 69 | def page = Page.build() 70 | 71 | then: 72 | assert page 73 | assert page.issue 74 | assert [page.id] == page.issue.pages.id 75 | } 76 | 77 | void testParentCollectionUpdatedWhenChildManuallyAdded() { 78 | def issue = new Issue(title: "title").save(failOnError: true) 79 | assert issue 80 | 81 | when: 82 | def page = Page.build(issue: issue) 83 | 84 | then: 85 | assert page 86 | assert page.issue == issue 87 | assert [page.id] == issue.pages.id 88 | } 89 | 90 | void testHasOne() { 91 | when: 92 | def arm = Arm.build() 93 | def hand = arm.hand 94 | 95 | then: 96 | assert arm 97 | assert hand 98 | assert hand.arm == arm 99 | assert arm.hand == hand 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/StandaloneUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import grails.buildtestdata.BuildDataTest 4 | import spock.lang.Specification 5 | import standalone.Standalone 6 | 7 | class StandaloneUnitTests extends Specification implements BuildDataTest { 8 | @Override 9 | Class[] getDomainClassesToMock() { 10 | [Standalone] 11 | } 12 | 13 | void testNullableBelongsToNotFollowed() { 14 | when: 15 | def standalone = build(Standalone) // standalone.Standalone has a "parent" property on it that is nullable (otherwise it'd get in an infinite loop) 16 | 17 | then: 18 | standalone 19 | standalone.id 20 | !standalone.parent 21 | standalone.emailAddress != null 22 | } 23 | 24 | void testBuildStandalonePassAllVariables() { 25 | def created = new Date() 26 | 27 | when: 28 | def obj = build(Standalone, [name: "Foo", age: 14, created: created, emailAddress: "foo@bar.com"]) 29 | 30 | then: 31 | assertValidDomainObject(obj) 32 | obj.name == "Foo" 33 | obj.age == 14 34 | obj.created == created 35 | "foo@bar.com" == obj.emailAddress 36 | } 37 | 38 | void testBuildStandalonePassNoVariables() { 39 | when: 40 | def obj = build(Standalone) 41 | 42 | then: 43 | assertValidDomainObject(obj) 44 | 45 | obj.name // by default it just uses the property name for the value for strings == "name" 46 | obj.created 47 | obj.age == 0 48 | } 49 | 50 | private void assertValidDomainObject(domainObject) { 51 | assert domainObject 52 | assert domainObject.id 53 | assert domainObject.errors.errorCount == 0 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/SubclassUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import grails.buildtestdata.BuildDataTest 4 | import spock.lang.Specification 5 | import subclassing.RelatedClass 6 | import subclassing.SubClass 7 | import subclassing.SuperClass 8 | 9 | class SubclassUnitTests extends Specification implements BuildDataTest { 10 | void setupSpec() { 11 | mockDomains(SubClass, RelatedClass, SuperClass) 12 | } 13 | 14 | void testSuccessfulBuildOfDomainSubclass() { 15 | when: 16 | def subClass = build(SubClass) 17 | 18 | then: 19 | assert subClass 20 | assert subClass.ident() > 0 21 | 22 | assert subClass.relatedClass.superClassInstances.contains(subClass) 23 | 24 | when: 25 | def relatedClass = build(RelatedClass) 26 | 27 | then: 28 | assert relatedClass 29 | assert relatedClass.ident() > 0 30 | 31 | when: 32 | def superClass = build(SuperClass) 33 | 34 | then: 35 | assert superClass 36 | assert superClass.ident() > 0 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/TestDataConfigUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import bookstore.Author 4 | import config.Article 5 | import config.Hotel 6 | import grails.buildtestdata.BuildDataTest 7 | import grails.buildtestdata.TestDataConfigurationHolder 8 | import spock.lang.Specification 9 | 10 | class TestDataConfigUnitTests extends Specification implements BuildDataTest { 11 | void cleanup() { 12 | // we should reset the config holder when feeding it values in tests as it could cause issues 13 | // for other tests later on that are expecting the default config if we do not 14 | TestDataConfigurationHolder.reset() 15 | } 16 | 17 | void testRealConfigFile() { 18 | mockDomains(Hotel) 19 | 20 | // uses the file in grails-app/conf/TestDataConfig.groovy 21 | TestDataConfigurationHolder.reset() // you can reset it if you want it to get to a known value 22 | 23 | when: 24 | Hotel testHotel = Hotel.build() 25 | 26 | then: 27 | "Motel 6" == testHotel.name 28 | testHotel.faxNumber.startsWith("612555") 29 | } 30 | 31 | void testStaticValue() { 32 | mockDomains(Hotel) 33 | 34 | def hotelNameAlwaysHilton = [('config.Hotel'): [name: "Hilton"]] 35 | TestDataConfigurationHolder.sampleData = hotelNameAlwaysHilton 36 | 37 | when: 38 | def hilton = Hotel.build() 39 | def stillHilton = Hotel.build() 40 | 41 | then: 42 | "Hilton" == hilton.name 43 | "Hilton" == stillHilton.name 44 | } 45 | 46 | void testConfigClosure() { 47 | mockDomains(Hotel) 48 | 49 | def i = 0 50 | def hotelNameAlternates = [ 51 | ('config.Hotel'): [name: { -> 52 | i++ % 2 == 0 ? "Holiday Inn" : "Hilton" 53 | }] 54 | ] 55 | TestDataConfigurationHolder.sampleData = hotelNameAlternates 56 | 57 | when: 58 | def holidayInn = Hotel.build() 59 | def hilton = Hotel.build() 60 | def backToHolidayInn = Hotel.build() 61 | 62 | then: 63 | holidayInn.name == "Holiday Inn" 64 | hilton.name == "Hilton" 65 | backToHolidayInn.name == "Holiday Inn" 66 | } 67 | 68 | void testAdditionalBuild() { 69 | mockDomains(Hotel) 70 | 71 | when: 72 | def hotel = Hotel.build() 73 | 74 | then: 75 | hotel.name == 'Motel 6' 76 | 77 | // Should also be able to build an article, even though we didn't 78 | // specifically include it and Hotel doesn't require it. 79 | when: 80 | def article = Article.build() 81 | 82 | then: 83 | article.name =~ 'Article' 84 | 85 | // Make sure we can use it 86 | Article.list().size() == 1 87 | } 88 | 89 | void testRecursiveAdditionalBuild() { 90 | mockDomains(Hotel) 91 | 92 | when: 93 | def hotel = Hotel.build() 94 | 95 | then: 96 | hotel.name == 'Motel 6' 97 | 98 | // Should also be able to build an article, even though we didn't 99 | // specifically include it and Hotel doesn't require it. 100 | when: 101 | def article = Article.build() 102 | 103 | then: 104 | article.name =~ 'Article' 105 | 106 | // Make sure we can use it 107 | Article.list().size() == 1 108 | Author.build() 109 | Author.list().size() == 1 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/ToManyBasicTypeSpec.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import bookstore.BookInfo 4 | import enums.BookType 5 | import grails.buildtestdata.BuildDataTest 6 | import grails.buildtestdata.TestDataConfigurationHolder 7 | import spock.lang.Specification 8 | 9 | class ToManyBasicTypeSpec extends Specification implements BuildDataTest { 10 | void setup() { 11 | TestDataConfigurationHolder.reset() 12 | } 13 | 14 | @Override 15 | Class[] getDomainClassesToMock() { 16 | [BookInfo] 17 | } 18 | 19 | void "build simple required enum property"() { 20 | when: 21 | BookInfo b = BookInfo.build(alternateNames: ['foo'], bookTypes: [BookType.Fiction]) 22 | 23 | then: 24 | b.primaryType in BookType.values() 25 | } 26 | 27 | void "build basic to-many String property"() { 28 | when: 29 | BookInfo b = BookInfo.build(primaryType: BookType.Fiction, bookTypes: [BookType.Fiction]) 30 | 31 | then: 32 | b.alternateNames 33 | } 34 | 35 | void "build basic to-many Enum property"() { 36 | when: 37 | BookInfo b = BookInfo.build(primaryType: BookType.Fiction, alternateNames: ['foo']) 38 | 39 | then: 40 | b.bookTypes 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/base/ValidateableBuilderSpec.groovy: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import grails.buildtestdata.TestDataBuilder 4 | import grails.buildtestdata.TestDataConfigurationHolder 5 | import grails.validation.Validateable 6 | import spock.lang.Specification 7 | 8 | class ValidateableBuilderSpec extends Specification implements TestDataBuilder { 9 | def "validateable instances can be built with default values"() { 10 | when: 11 | def obj = build(TestCommand) 12 | 13 | then: 14 | obj.name == "name." 15 | obj.age == 0 16 | !obj.isActive 17 | } 18 | 19 | def "validateable instances can be built with override values"() { 20 | when: 21 | def obj = build(TestCommand, [name: 'Aaron', isActive: true]) 22 | 23 | then: 24 | obj.name == "Aaron" 25 | obj.age == 0 26 | obj.isActive 27 | } 28 | 29 | def "validateable instances can be built with sampleData"() { 30 | given: 31 | TestDataConfigurationHolder.sampleData = [('base.TestCommand'): [name: 'Bob']] 32 | 33 | when: 34 | def obj = build(TestCommand, [isActive: true]) 35 | 36 | then: 37 | obj.name == "Bob" 38 | obj.age == 0 39 | obj.isActive 40 | } 41 | 42 | def "default validateable instances can be built with defaults"() { 43 | when: 44 | def obj = build(NullableTestCommand) 45 | 46 | then: "these are nullable by default now" 47 | obj.name == null 48 | obj.isActive == null 49 | 50 | and: "this is non null and should be set" 51 | obj.age == 0 52 | } 53 | 54 | void cleanupSpec() { 55 | TestDataConfigurationHolder.reset() 56 | } 57 | } 58 | 59 | @SuppressWarnings("GroovyUnusedDeclaration") 60 | class TestCommand implements Validateable { 61 | String name 62 | Integer age 63 | Boolean isActive 64 | 65 | static constraints = { 66 | name(nullable: false, maxSize: 10, minSize: 5) 67 | age(nullable: false, min: 0) 68 | } 69 | } 70 | 71 | @SuppressWarnings("GroovyUnusedDeclaration") 72 | class NullableTestCommand implements Validateable { 73 | String name 74 | Integer age 75 | Boolean isActive 76 | 77 | static constraints = { 78 | age(nullable: false, min: 0) 79 | } 80 | 81 | static boolean defaultNullable() { 82 | true 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/embedded/EmbeddingUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package embedded 2 | 3 | import grails.buildtestdata.BuildDataTest 4 | import grails.buildtestdata.TestDataConfigurationHolder 5 | import spock.lang.Specification 6 | 7 | class EmbeddingUnitTests extends Specification implements BuildDataTest { 8 | @Override 9 | Class[] getDomainClassesToMock() { 10 | [Embedding] 11 | } 12 | 13 | void "embedded property with no default initial value"() { 14 | TestDataConfigurationHolder.sampleData = [:] 15 | 16 | when: 17 | Embedding e = build(Embedding) 18 | 19 | then: 20 | e.inner.someValue == 'someValue' 21 | } 22 | 23 | void "embedded property with default initial value as object"() { 24 | TestDataConfigurationHolder.sampleData = [('embedded.Embedding'): [inner: new Embedded(someValue: 'test')]] 25 | 26 | when: 27 | Embedding e = build(Embedding) 28 | 29 | then: 30 | e.inner.someValue == 'test' 31 | } 32 | 33 | void "embedded property with default initial value as map"() { 34 | TestDataConfigurationHolder.sampleData = [('embedded.Embedding'): [inner: [someValue: 'test']]] 35 | 36 | when: 37 | Embedding e = build(Embedding) 38 | 39 | then: 40 | e.inner.someValue == 'test' 41 | } 42 | 43 | void "embedded property with global default for the embedded class"() { 44 | TestDataConfigurationHolder.sampleData = [('embedded.Embedded'): [someValue: 'globalDefault']] 45 | 46 | when: 47 | Embedding e = build(Embedding) 48 | 49 | then: 50 | e.inner.someValue == 'globalDefault' 51 | } 52 | 53 | void cleanupSpec() { 54 | TestDataConfigurationHolder.reset() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/hibernate4/PaintingBuildSpec.groovy: -------------------------------------------------------------------------------- 1 | package hibernate4 2 | 3 | import grails.buildtestdata.BuildDataTest 4 | import spock.lang.Specification 5 | 6 | class PaintingBuildSpec extends Specification implements BuildDataTest { 7 | @Override 8 | Class[] getDomainClassesToMock() { 9 | [Painting] 10 | } 11 | 12 | void "building a Painting builds Gallery and Painter"() { 13 | when: 14 | Painting painting = build(Painting, [title: "The Hunters in the Snow"]) 15 | 16 | then: 17 | painting != null 18 | painting.id != null 19 | painting.title == "The Hunters in the Snow" 20 | painting.painter.name != null 21 | painting.gallery.name != null 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/list/EstablishedAuthorUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import bookstore.EstablishedAuthor 4 | import grails.buildtestdata.BuildDataTest 5 | import spock.lang.Specification 6 | 7 | class EstablishedAuthorUnitTests extends Specification implements BuildDataTest { 8 | @Override 9 | Class[] getDomainClassesToMock() { 10 | [EstablishedAuthor] 11 | } 12 | 13 | void testRequiredListAndSetOk() { 14 | when: 15 | EstablishedAuthor establishedAuthor = build(EstablishedAuthor, [name: "Steven King"]) 16 | then: 17 | establishedAuthor.hardcoverBooks != null 18 | establishedAuthor.hardcoverBooks.size() == 1 19 | establishedAuthor.paperbackBooks != null 20 | establishedAuthor.paperbackBooks.size() == 1 21 | establishedAuthor.metaData.tags != null 22 | establishedAuthor.metaData.tags.size() == 2 23 | establishedAuthor.id > 0 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/list/SantaUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import grails.buildtestdata.BuildDataTest 4 | import spock.lang.Specification 5 | 6 | class SantaUnitTests extends Specification implements BuildDataTest { 7 | @Override 8 | Class[] getDomainClassesToMock() { 9 | [Santa, Child] 10 | } 11 | 12 | void testChildListOk() { 13 | when: 14 | def santa = build(Santa, [firstName: 'Santa', lastName: 'Claus']) 15 | then: 16 | santa.children == null 17 | 18 | when: 19 | build(Child, [name: 'ivan', grade: 2, dateOfBirth: new Date(), santa: santa]) 20 | then: 21 | santa.children.size() == 1 22 | 23 | when: 24 | build(Child, [name: 'hazel', grade: 0, dateOfBirth: new Date(), santa: santa]) 25 | 26 | then: 27 | santa.children.size() == 2 28 | santa.save(flush: true) 29 | santa.children.size() == 2 30 | santa.id > 0 31 | } 32 | 33 | void testElvesListMinConstraintOk() { 34 | when: 35 | def santa = build(Santa, [firstName: 'Santa', lastName: 'Claus']) 36 | 37 | then: 38 | santa.children == null 39 | santa.elves != null 40 | santa.elves.size() == 1 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/groovy/triangle/TriangleRelationshipUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package triangle 2 | 3 | import grails.buildtestdata.BuildDataTest 4 | import spock.lang.Specification 5 | 6 | class TriangleRelationshipUnitTests extends Specification implements BuildDataTest { 7 | @Override 8 | Class[] getDomainClassesToMock() { 9 | [Worker, Manager, Director] 10 | } 11 | 12 | void testBuildTriangleRelationship() { 13 | // director has many managers and workers 14 | // manager belongsto director and has many workers 15 | // workers belongs to a director and a manager, we should be able to build any one of these successfully 16 | when: 17 | def w = build(Worker) 18 | then: 19 | w instanceof Worker 20 | w.manager 21 | w.director 22 | 23 | when: 24 | def m = build(Manager) 25 | then: 26 | m instanceof Manager 27 | m.director 28 | 29 | when: 30 | def d = build(Director) 31 | then: 32 | d instanceof Director 33 | } 34 | 35 | void testBuildTriangleRelationshipPartiallyCompleteAlready() { 36 | when: 37 | def manager = build(Manager) 38 | then: 39 | manager 40 | 41 | when: 42 | def director = build(Director, [managers: [manager]]) 43 | then: 44 | director 45 | 46 | when: 47 | def w = build(Worker, [manager: manager]) 48 | then: 49 | w.manager 50 | 51 | when: 52 | w = build(Worker, [director: director]) 53 | then: 54 | w.director 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/bookStore/src/test/resources/TestDataConfig.groovy: -------------------------------------------------------------------------------- 1 | import abstractclass.ConcreteSubClass 2 | import bookstore.Author 3 | import config.Article 4 | import embedded.Embedded 5 | 6 | testDataConfig { 7 | 8 | // For unit tests, this indicates an implicit @Build relationship. In this case, anytime a Hotel is used in @Build 9 | // we also want to include Article and Author. This is useful if you define defaults in sampleData that explicitly 10 | // build other objects. 11 | unitAdditionalBuild = [ 12 | 'config.Hotel': [Article], 13 | 'config.Article': [Author] 14 | ] 15 | 16 | // For polymorphic associations, this allows you to default the concrete class that is built automatically. 17 | // By default, BTD will find all concrete subclasses and build the first one alphabetically by name. 18 | abstractDefault = [ 19 | 'abstractclass.AbstractClass': ConcreteSubClass, 20 | 'abstractclass.AbstractSubClass': ConcreteSubClass 21 | ] 22 | 23 | sampleData { 24 | // Hotel class is in "config" package so we use a string in the builder 25 | 'config.Hotel' { 26 | // returns "Motel 6" for the name property whenever a Hotel is constructed 27 | // and a name is not already given 28 | name = "Motel 6" 29 | 30 | // returns a unique fax number at each request 31 | def i = 6125551111 32 | faxNumber = {-> "${i++}" } // creates "6125551111", "6125551112", .... 33 | } 34 | 'config.Article' { 35 | def i = 1 36 | name = {-> "Article ${i++}" } 37 | } 38 | 'bookstore.EstablishedAuthor' { 39 | metaData = ['tags': ['fiction', 'horror']] 40 | } 41 | 42 | // work around for embedded objects in src/groovy 43 | // grails does not create Artefacts that we can query for constraints for things outside of grails-app/domain 44 | 'embedded.Embedding' { 45 | inner = {-> new Embedded(someValue: "value") } 46 | } 47 | 48 | 'standalone.ParentWithAssignedKey' { 49 | def i = 1 50 | id = {-> "pid${i++}" } 51 | } 52 | 'standalone.ChildWithAssignedKey' { 53 | def i = 1 54 | id = {-> "cid${i++}" } 55 | } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | projectVersion=6.0.0-SNAPSHOT 2 | 3 | grailsVersion=7.0.0-M1 4 | grailsGradlePluginVersion=7.0.0-M3 5 | gorm.version=9.0.0-M3 6 | 7 | org.gradle.daemon=true 8 | org.gradle.parallel=true 9 | org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1024M 10 | -------------------------------------------------------------------------------- /gradle/docs.gradle: -------------------------------------------------------------------------------- 1 | tasks.register('cleanDocs', Delete) { 2 | delete file("${rootProject.buildDir}/docs") 3 | } 4 | 5 | tasks.register('aggregateGroovyApiDoc', Groovydoc) { 6 | def groovyDocProjects = subprojects.findAll { it.name in ['build-test-data'] } 7 | dependsOn = [tasks.named('cleanDocs')] + groovyDocProjects.collect { it.tasks.named('groovydoc') } 8 | 9 | description = 'Generates Groovy API Documentation for all plugin projects under rootDir/gapi' 10 | 11 | group = JavaBasePlugin.DOCUMENTATION_GROUP 12 | access = GroovydocAccess.PROTECTED 13 | includeAuthor = false 14 | includeMainForScripts = true 15 | processScripts = true 16 | source = groovyDocProjects.groovydoc.source 17 | destinationDir = file("${rootProject.buildDir}/docs/gapi") 18 | classpath = files(groovyDocProjects.groovydoc.classpath) 19 | groovyClasspath = files(groovyDocProjects.groovydoc.groovyClasspath) 20 | } 21 | 22 | tasks.register('docs') { 23 | group = JavaBasePlugin.DOCUMENTATION_GROUP 24 | dependsOn = ['aggregateGroovyApiDoc', 'docs:asciidoctor'] 25 | finalizedBy 'copyAsciiDoctorDocs', 'ghPagesRootIndexPage' 26 | } 27 | 28 | tasks.register('copyAsciiDoctorDocs', Copy) { 29 | group = JavaBasePlugin.DOCUMENTATION_GROUP 30 | dependsOn = ['docs'] 31 | from "${rootProject.allprojects.find { it.name == 'docs'}.projectDir}/build" 32 | includes = ['docs/**'] 33 | into rootProject.buildDir 34 | includeEmptyDirs = false 35 | } 36 | 37 | tasks.register('ghPagesRootIndexPage', Copy) { 38 | group = 'documentation' 39 | dependsOn = ['docs'] 40 | from file("${rootProject.allprojects.find { it.name == 'docs'}.projectDir}/src/docs/index.tmpl") 41 | into rootProject.buildDir.toPath().resolve('docs').toFile() 42 | rename 'index.tmpl', 'ghpages.html' 43 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/longwa/build-test-data/a13892cb986ef38dd0cfb4eca8f0cc8c944b5a22/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /plugin/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url "https://repo.grails.org/grails/core" } 4 | } 5 | dependencies { 6 | classpath "org.grails:grails-gradle-plugin:$grailsGradlePluginVersion" 7 | } 8 | } 9 | 10 | group "org.grails.plugins" 11 | apply plugin: "eclipse" 12 | apply plugin: "idea" 13 | apply plugin: "org.grails.grails-plugin" 14 | 15 | dependencies { 16 | implementation "org.springframework.boot:spring-boot-starter-logging" 17 | implementation "org.springframework.boot:spring-boot-autoconfigure" 18 | implementation "dk.brics:automaton:1.12-4" 19 | 20 | implementation "org.grails:grails-core" 21 | implementation "org.grails:grails-gorm-testing-support" 22 | implementation "org.grails:grails-databinding" 23 | implementation "org.grails:grails-datastore-gorm" 24 | implementation "org.grails:grails-console" 25 | 26 | // So we can run 'grails' and/or 'grails test-app' inside build-test-data 27 | profile "org.grails.profiles:web-plugin" 28 | 29 | implementation "org.springframework.boot:spring-boot-starter-actuator" 30 | implementation "org.springframework.boot:spring-boot-starter-tomcat" 31 | implementation "org.grails:grails-web-boot" 32 | 33 | runtimeOnly 'org.apache.groovy:groovy-dateutil' 34 | 35 | testImplementation "org.grails.plugins:hibernate5" 36 | testImplementation "com.h2database:h2" 37 | testImplementation "org.apache.tomcat:tomcat-jdbc" 38 | 39 | testImplementation "org.grails:grails-gorm-testing-support" 40 | testImplementation "org.grails:grails-web-testing-support" 41 | } 42 | 43 | // Enable if you wish to package this plugin as a standalone application 44 | bootJar.enabled = false 45 | 46 | //this makes the groovy docs much more readable by shorting packages and providing links 47 | groovydoc { 48 | excludes = ['**/*GrailsPlugin.groovy', '**/Application.groovy'] 49 | link('https://docs.oracle.com/en/java/javase/17/docs/api', 'java.', 'org.xml', 'org.xml.') 50 | link('https://jakarta.ee/specifications/platform/9/apidocs', 'jakarta.') 51 | link("https://docs.spring.io/spring/docs/6.1.x/javadoc-api", 'org.springframework') 52 | link('https://docs.groovy-lang.org/4.0.24/html/gapi', 'groovy.', 'org.apache.groovy.') 53 | link('https://docs.grails.org/7.0.0/api', 'grails.', 'org.grails.') 54 | link('https://testing.grails.org/latest/api', 'grails.testing.', 'org.grails.testing.') 55 | link('http://gorm.grails.org/9.0.x/hibernate/api/', 'grails.gorm', 'grails.orm', 'org.grails.datastore', 'org.grails.orm') 56 | } -------------------------------------------------------------------------------- /plugin/grails-app/conf/application.yml: -------------------------------------------------------------------------------- 1 | --- 2 | grails: 3 | profile: web-plugin 4 | codegen: 5 | defaultPackage: build.test.data 6 | info: 7 | app: 8 | name: '@info.app.name@' 9 | version: '@info.app.version@' 10 | grailsVersion: '@info.app.grailsVersion@' 11 | spring: 12 | groovy: 13 | template: 14 | check-template-location: false 15 | 16 | --- 17 | grails: 18 | mime: 19 | disable: 20 | accept: 21 | header: 22 | userAgents: 23 | - Gecko 24 | - WebKit 25 | - Presto 26 | - Trident 27 | types: 28 | all: '*/*' 29 | atom: application/atom+xml 30 | css: text/css 31 | csv: text/csv 32 | form: application/x-www-form-urlencoded 33 | html: 34 | - text/html 35 | - application/xhtml+xml 36 | js: text/javascript 37 | json: 38 | - application/json 39 | - text/json 40 | multipartForm: multipart/form-data 41 | rss: application/rss+xml 42 | text: text/plain 43 | hal: 44 | - application/hal+json 45 | - application/hal+xml 46 | xml: 47 | - text/xml 48 | - application/xml 49 | urlmapping: 50 | cache: 51 | maxsize: 1000 52 | controllers: 53 | defaultScope: singleton 54 | converters: 55 | encoding: UTF-8 56 | hibernate: 57 | cache: 58 | queries: false 59 | views: 60 | default: 61 | codec: html 62 | gsp: 63 | encoding: UTF-8 64 | htmlcodec: xml 65 | codecs: 66 | expression: html 67 | scriptlets: html 68 | taglib: none 69 | staticparts: none 70 | 71 | --- 72 | dataSource: 73 | pooled: true 74 | jmxExport: true 75 | driverClassName: org.h2.Driver 76 | username: sa 77 | password: 78 | 79 | hibernate: 80 | dialect: org.hibernate.dialect.H2Dialect 81 | 82 | environments: 83 | development: 84 | dataSource: 85 | dbCreate: create-drop 86 | url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE 87 | test: 88 | dataSource: 89 | dbCreate: update 90 | url: jdbc:h2:mem:testDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE 91 | production: 92 | dataSource: 93 | dbCreate: update 94 | url: jdbc:h2:prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE 95 | properties: 96 | jmxEnabled: true 97 | initialSize: 5 98 | maxActive: 50 99 | minIdle: 5 100 | maxIdle: 25 101 | maxWait: 10000 102 | maxAge: 600000 103 | timeBetweenEvictionRunsMillis: 5000 104 | minEvictableIdleTimeMillis: 60000 105 | validationQuery: SELECT 1 106 | validationQueryTimeout: 3 107 | validationInterval: 15000 108 | testOnBorrow: true 109 | testWhileIdle: true 110 | testOnReturn: false 111 | jdbcInterceptors: ConnectionState 112 | defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED 113 | -------------------------------------------------------------------------------- /plugin/grails-app/conf/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /plugin/grails-app/init/grails/plugins/buildtestdata/Application.groovy: -------------------------------------------------------------------------------- 1 | package grails.plugins.buildtestdata 2 | 3 | import grails.boot.GrailsApp 4 | import grails.boot.config.GrailsAutoConfiguration 5 | 6 | class Application extends GrailsAutoConfiguration { 7 | static void main(String[] args) { 8 | GrailsApp.run(Application, args) 9 | } 10 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/BuildDataTest.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata 2 | 3 | import grails.buildtestdata.mixin.Build 4 | import grails.buildtestdata.testing.DependencyDataTest 5 | import grails.buildtestdata.utils.MetaHelper 6 | import grails.testing.spock.OnceBefore 7 | import groovy.transform.CompileDynamic 8 | import groovy.transform.CompileStatic 9 | 10 | /** 11 | * Unit tests should implement this trait to add build-test-data functionality. 12 | * Meant as a drop in replacement for Grails Testing Support's DataTest 13 | */ 14 | @CompileStatic 15 | @SuppressWarnings("GroovyUnusedDeclaration") 16 | trait BuildDataTest extends DependencyDataTest implements TestDataBuilder { 17 | 18 | @Override 19 | void mockDomains(Class... domainClassesToMock) { 20 | super.mockDomains(domainClassesToMock) 21 | 22 | // Add build methods 23 | Class[] domainClasses = dataStore.mappingContext.getPersistentEntities()*.javaClass 24 | MetaHelper.addBuildMetaMethods(domainClasses) 25 | } 26 | 27 | @OnceBefore 28 | void handleBuildAnnotation() { 29 | Build build = this.getClass().getAnnotation(Build) 30 | if (build) { 31 | mockDomains(build.value()) 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/BuildDomainTest.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata 2 | 3 | import groovy.transform.CompileStatic 4 | import org.springframework.core.GenericTypeResolver 5 | 6 | /** 7 | * Should works as a drop in replacement for the Grails Testing Support's 8 | * grails.testing.gorm.DomainUnitTest for testing a single entity using Generics 9 | * Its walks the tree so if you have a Book that has a required Author association you only need to do 10 | * implement BuildDomainTest and it will take care of mocking the Author for you. 11 | */ 12 | @CompileStatic 13 | trait BuildDomainTest implements BuildDataTest { 14 | 15 | private D entity 16 | private static Class entityClass 17 | 18 | /** 19 | * keeps the method consistent with DomainUnitTest with the exception that this calls save(failOnError:true) by default 20 | * to change just pass in args. getDomain(save: false) for example will set it up and not call save at all. 21 | * getDomain(failOnError:false) would call save with throwing an exception on validation. 22 | * @return An instance of the domain class, 23 | */ 24 | D getDomain(Map args = [:]) { 25 | getEntity(args) 26 | } 27 | 28 | /** 29 | * an entity instance of the Gorm class set as the generic. both getDomain and getEntity are the same thing 30 | */ 31 | D getEntity(Map args = [:]) { 32 | if (entity == null) { 33 | this.entity = TestData.build(args, getEntityClass()) 34 | } 35 | entity 36 | } 37 | 38 | /** this is called by the {@link org.grails.testing.gorm.spock.DataTestSetupSpecInterceptor} */ 39 | @Override 40 | Class[] getDomainClassesToMock() { 41 | [getEntityClass()].toArray(Class) 42 | } 43 | 44 | Class getEntityClass() { 45 | if (!entityClass) 46 | this.entityClass = (Class) GenericTypeResolver.resolveTypeArgument(getClass(), BuildDomainTest.class) 47 | 48 | return entityClass 49 | } 50 | 51 | D build() { 52 | build([:], getEntityClass(), [:]) 53 | } 54 | 55 | D build(Map args) { 56 | build(args, getEntityClass()) 57 | } 58 | 59 | D build(Map args, Map propValues) { 60 | build(args, getEntityClass(), propValues) 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/BuildHibernateTest.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata 2 | 3 | import grails.buildtestdata.utils.MetaHelper 4 | import grails.testing.spock.OnceBefore 5 | import groovy.transform.CompileStatic 6 | 7 | /** 8 | * Support build test data functionality for unit tests that extend HibernateSpec 9 | * 10 | * @since 3.3.1 11 | */ 12 | @CompileStatic 13 | @SuppressWarnings("GroovyUnusedDeclaration") 14 | trait BuildHibernateTest implements TestDataBuilder { 15 | /** 16 | * HibernateSpec must already override this method to provide the domains they are using 17 | */ 18 | abstract List getDomainClasses() 19 | 20 | @OnceBefore 21 | void setupBuildMethods() { 22 | MetaHelper.addBuildMetaMethods(getDomainClasses().toArray(Class)) 23 | } 24 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/BuildTestDataGrailsPlugin.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata 2 | 3 | import grails.plugins.Plugin 4 | import groovy.transform.CompileDynamic 5 | 6 | //@CompileStatic 7 | @SuppressWarnings("GroovyUnusedDeclaration") 8 | class BuildTestDataGrailsPlugin extends Plugin { 9 | def grailsVersion = "3.3.0 > *" 10 | 11 | def title = "Build Test Data Plugin" 12 | def description = 'Enables the easy creation of test data by automatically satisfying most constraints.' 13 | def license = "APACHE" 14 | def documentation = "https://longwa.github.io/build-test-data" 15 | 16 | def developers = [ 17 | [name: "Aaron Long", email: "aaron@aaronlong.me"], 18 | [name: "Ted Naleid", email: "contact@naleid.com"], 19 | ] 20 | 21 | def issueManagement = [system: 'github', url: 'https://github.com/longwa/build-test-data/issues'] 22 | def scm = [url: 'https://github.com/longwa/build-test-data/'] 23 | 24 | @Override 25 | void doWithApplicationContext() { 26 | Class[] domainClasses = grailsApplication.domainClasses*.clazz 27 | addBuildMetaMethods(domainClasses) 28 | } 29 | 30 | @CompileDynamic 31 | void addBuildMetaMethods(Class... entityClasses){ 32 | entityClasses.each { ec -> 33 | def mc = ec.metaClass 34 | //println("adding gradmeta for $ec") 35 | mc.static.build = { 36 | return TestData.build(ec) 37 | } 38 | mc.static.build = { Map args -> 39 | return TestData.build(args, ec) 40 | } 41 | mc.static.build = { Map args, Map data -> 42 | return TestData.build(args, ec, data) 43 | } 44 | mc.static.findOrBuild = { 45 | return TestData.findOrBuild( ec, [:]) 46 | } 47 | mc.static.findOrBuild = { Map data -> 48 | return TestData.findOrBuild( ec, data) 49 | } 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/TestDataBuilder.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata 2 | 3 | import groovy.transform.CompileStatic 4 | import org.junit.AfterClass 5 | import org.junit.Before 6 | import org.junit.jupiter.api.AfterAll 7 | import org.junit.jupiter.api.BeforeEach 8 | 9 | /** 10 | * Integration tests, any class really, can implement this trait to add build-test-data functionality 11 | */ 12 | @CompileStatic 13 | @SuppressWarnings("GroovyUnusedDeclaration") 14 | trait TestDataBuilder { 15 | private static boolean hasCustomTestDataConfig = false 16 | 17 | /** calls {@link TestData#build} */ 18 | public T build(Map args = [:], Class clazz) { 19 | TestData.build(args, clazz) 20 | } 21 | 22 | /** calls {@link TestData#build} */ 23 | public T build(Class clazz, Map propValues) { 24 | TestData.build([:], clazz, propValues) 25 | } 26 | 27 | /** calls {@link TestData#build} */ 28 | public T build(Map args, Class clazz, Map propValues) { 29 | TestData.build(args, clazz, propValues) 30 | } 31 | 32 | /** calls {@link TestData#build} with [find: true] passed to args*/ 33 | public T findOrBuild(Class clazz, Map propValues = [:]) { 34 | TestData.build([find: true], clazz, propValues) 35 | } 36 | 37 | /** 38 | * Override this to override test data configuration for this test class 39 | */ 40 | Closure doWithTestDataConfig() { 41 | null 42 | } 43 | 44 | @BeforeEach 45 | void setupCustomTestDataConfig() { 46 | Closure testDataConfig = doWithTestDataConfig() 47 | if (testDataConfig) { 48 | TestDataConfigurationHolder.mergeConfig(testDataConfig) 49 | hasCustomTestDataConfig = true 50 | } 51 | } 52 | 53 | @AfterAll 54 | static void cleanupTestDataBuilder() { 55 | TestData.clear() 56 | if (hasCustomTestDataConfig) { 57 | hasCustomTestDataConfig = false 58 | TestDataConfigurationHolder.reset() 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/UnitTestDataBuilder.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata 2 | 3 | @Deprecated //use BuildDataTest instead. left in as it was here for 3.3.0-RC1. will keep for one more iteration while users refactor 4 | trait UnitTestDataBuilder extends BuildDataTest{ 5 | 6 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/builders/DataBuilder.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.builders 2 | 3 | interface DataBuilder { 4 | /** 5 | * builds the data using the passed in context 6 | * @param ctx the DataBuilderContext 7 | * @return the built entity. 8 | */ 9 | def build(DataBuilderContext ctx) 10 | 11 | /** 12 | * builds the data using the passed in context 13 | * 14 | * @param args optional argument map
15 | * - save : (default: true) whether to call the save method when its a GormEntity
16 | * - find : (default: false) whether to try and find the entity in the datastore first
17 | * - flush : (default: false) passed in the args to the GormEntity save method
18 | * - failOnError : (default: true) passed in the args to the GormEntity save method
19 | * - include : a list of the properties to build in addition to the required fields. use `*` to build all
20 | * @param ctx the DataBuilderContext 21 | * @return the built entity. 22 | */ 23 | def build(Map args, DataBuilderContext ctx) 24 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/builders/DataBuilderContext.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.builders 2 | 3 | import grails.buildtestdata.TestData 4 | import groovy.transform.CompileStatic 5 | 6 | /** 7 | * a context object that is passed around 8 | * holds the data and caches the know instances that are already created 9 | */ 10 | @CompileStatic 11 | class DataBuilderContext { 12 | Map data 13 | Object target 14 | 15 | Map knownInstances = [:] 16 | 17 | // The fields to include along with the required fields. will either be a List or a String = '*' for all 18 | Object includes 19 | 20 | DataBuilderContext() { 21 | this.data = [:] 22 | } 23 | 24 | DataBuilderContext(Map data) { 25 | this.data = data 26 | } 27 | 28 | /** 29 | * 30 | * @param instance the instance to use 31 | * @param property the property to build on the instance 32 | * @param propertyType the Class for the property 33 | * @param save true|false on whether to call save 34 | * @return 35 | */ 36 | Object satisfyNested(Object instance, String property, Class propertyType, boolean save = false) { 37 | //it the property is already set then just return it 38 | if (instance[property]) { 39 | return instance[property] 40 | } 41 | 42 | if (propertyType.isAssignableFrom(instance.class)) { 43 | return instance 44 | } 45 | 46 | //see if it exists in the knownInstances already and use it if so 47 | def match = knownInstances.find { k, v -> propertyType.isAssignableFrom(k) } 48 | if (match) { 49 | return match.value 50 | } 51 | 52 | DataBuilderContext newCtx = createCopy(property) 53 | newCtx.target = instance[property] 54 | 55 | try { 56 | knownInstances.put(instance.class, instance) 57 | 58 | // The save is primarily here and will be true for ManyToOne so we don't get 59 | // org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation 60 | return TestData.findBuilder(propertyType).build(newCtx, save: save) 61 | } 62 | finally { 63 | knownInstances.remove(instance.class) 64 | } 65 | } 66 | 67 | /** 68 | * doesn't use knownInstances cache and creates a new object for the property 69 | */ 70 | Object satisfyNestedWithNew(Object instance, String property, Class propertyType) { 71 | DataBuilderContext newCtx = createCopy(property) 72 | knownInstances.put(instance.class, instance) 73 | 74 | try { 75 | return TestData.findBuilder(propertyType).build(newCtx, save: false) 76 | } 77 | finally { 78 | knownInstances.remove(instance.class) 79 | } 80 | 81 | } 82 | 83 | /** 84 | * Clones this context and sets drills down the for the property if it exists 85 | * 86 | * @param property 87 | * @return 88 | */ 89 | DataBuilderContext createCopy(String property) { 90 | def newCtx = new DataBuilderContext() 91 | newCtx.knownInstances = knownInstances 92 | 93 | if (data[property]) { 94 | newCtx.data = (Map) data[property] 95 | } 96 | 97 | return newCtx 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/builders/DataBuilderFactory.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.builders 2 | 3 | interface DataBuilderFactory{ 4 | T build(Class target) 5 | boolean supports(Class clazz) 6 | } 7 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/builders/PogoDataBuilder.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.builders 2 | 3 | import grails.buildtestdata.TestDataConfigurationHolder 4 | import grails.buildtestdata.utils.Basics 5 | import grails.buildtestdata.utils.DomainUtil 6 | import grails.databinding.DataBinder 7 | import grails.databinding.SimpleDataBinder 8 | import grails.databinding.SimpleMapDataBindingSource 9 | import groovy.transform.CompileStatic 10 | import org.springframework.core.Ordered 11 | import org.springframework.core.annotation.Order 12 | 13 | @CompileStatic 14 | class PogoDataBuilder implements DataBuilder { 15 | 16 | @Order(Ordered.LOWEST_PRECEDENCE) 17 | static class Factory implements DataBuilderFactory { 18 | @Override 19 | PogoDataBuilder build(Class target) { 20 | return new PogoDataBuilder(target) 21 | } 22 | 23 | @Override 24 | boolean supports(Class clazz) { 25 | return true 26 | } 27 | } 28 | 29 | DataBinder dataBinder 30 | Class targetClass 31 | 32 | PogoDataBuilder(Class targetClass) { 33 | // findConcreteSubclass takes care of subtituing in concrete classes for abstracts 34 | this.targetClass = DomainUtil.findConcreteSubclass(targetClass) 35 | this.dataBinder = new SimpleDataBinder() 36 | } 37 | 38 | @Override 39 | def build(DataBuilderContext ctx) { 40 | return build([:], ctx) 41 | } 42 | 43 | @Override 44 | def build(Map args, DataBuilderContext ctx) { 45 | return doBuild(ctx) 46 | } 47 | 48 | def doBuild(DataBuilderContext ctx) { 49 | // Nothing to do, target exists already 50 | if (ctx.target) { 51 | return ctx.target 52 | } 53 | 54 | // Create a new empty instance 55 | def instance = getNewInstance() 56 | 57 | Map initialProps = findMissingConfigValues(ctx.data, instance) 58 | if (initialProps) { 59 | if (ctx.data) { 60 | ctx.data = [:] + initialProps + ctx.data 61 | } 62 | else { 63 | ctx.data = [:] + initialProps 64 | } 65 | } 66 | if (ctx.data) { 67 | dataBinder.bind(instance, new SimpleMapDataBindingSource(ctx.data)) 68 | } 69 | 70 | instance 71 | } 72 | 73 | Map findMissingConfigValues(Map propValues, Object newInstance) { 74 | Set missingProperties = TestDataConfigurationHolder.getConfigPropertyNames(targetClass.name) - propValues.keySet() 75 | TestDataConfigurationHolder.getPropertyValues(targetClass.name, newInstance, missingProperties, propValues) 76 | } 77 | 78 | def getNewInstance() { 79 | if (List.isAssignableFrom(targetClass)) { 80 | [] as List 81 | } 82 | else if (Set.isAssignableFrom(targetClass)) { 83 | [] as Set 84 | } 85 | else if (targetClass.isEnum()) { 86 | Basics.getDefaultValue(targetClass) 87 | } 88 | else { 89 | targetClass.newInstance() 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/AbstractHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.buildtestdata.builders.DataBuilderContext 4 | import grails.gorm.validation.ConstrainedProperty 5 | import grails.gorm.validation.Constraint 6 | import groovy.transform.CompileStatic 7 | import org.codehaus.groovy.runtime.InvokerHelper 8 | 9 | @CompileStatic 10 | abstract class AbstractHandler implements ConstraintHandler { 11 | 12 | @Override 13 | void handle(Object instance, String propertyName, Constraint appliedConstraint, 14 | ConstrainedProperty constrainedProperty, DataBuilderContext ctx) { 15 | handle(instance, propertyName, appliedConstraint, constrainedProperty) 16 | handle(instance, propertyName, appliedConstraint) 17 | handle(instance, propertyName, constrainedProperty) 18 | handle(instance, propertyName) 19 | } 20 | 21 | void handle(Object instance, String propertyName, Constraint appliedConstraint, ConstrainedProperty constrainedProperty) {} 22 | 23 | void handle(Object instance, String propertyName, Constraint appliedConstraint) {} 24 | 25 | void handle(Object instance, String propertyName, ConstrainedProperty constrainedProperty) {} 26 | 27 | void handle(Object instance, String propertyName) {} 28 | 29 | void setValue(Object instance, String property, Object value) { 30 | InvokerHelper.setProperty(instance, property, value) 31 | } 32 | 33 | Object getValue(Object instance, String property) { 34 | InvokerHelper.getProperty(instance, property); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/AssociationMinSizeHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.buildtestdata.builders.DataBuilderContext 4 | import grails.gorm.validation.ConstrainedProperty 5 | import groovy.transform.CompileStatic 6 | import org.grails.datastore.gorm.GormEntity 7 | import org.grails.datastore.mapping.model.PersistentEntity 8 | import org.grails.datastore.mapping.model.PersistentProperty 9 | import org.grails.datastore.mapping.model.types.Association 10 | 11 | @CompileStatic 12 | class AssociationMinSizeHandler extends MinSizeConstraintHandler{ 13 | PersistentEntity persistentEntity 14 | 15 | AssociationMinSizeHandler(PersistentEntity persistentEntity){ 16 | this.persistentEntity=persistentEntity 17 | } 18 | 19 | @Override 20 | void handle(Object instance, String propertyName, ConstrainedProperty constrained, DataBuilderContext ctx, 21 | int minSize, Object propertyValue) { 22 | PersistentProperty property = persistentEntity.getPropertyByName(propertyName) 23 | if(property instanceof Association){ 24 | Integer size = (Integer)propertyValue.invokeMethod('size',null) 25 | if (size < minSize) { 26 | ((size + 1)..minSize).each { 27 | Class referencedClass = ((Association)property).associatedEntity.javaClass 28 | def obj = ctx.satisfyNestedWithNew(instance, propertyName, referencedClass) 29 | ((GormEntity) instance).addTo(propertyName, obj) 30 | } 31 | } 32 | 33 | } else { 34 | super.handle(instance, propertyName, constrained, ctx, minSize, propertyValue) 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/BlankConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.gorm.validation.ConstrainedProperty 4 | import grails.gorm.validation.Constraint 5 | import groovy.transform.CompileStatic 6 | 7 | @CompileStatic 8 | class BlankConstraintHandler extends AbstractHandler { 9 | 10 | @Override 11 | void handle(Object instance, String propertyName, Constraint appliedConstraint, ConstrainedProperty constrainedProperty) { 12 | // shouldn't get here, as nullableHandler fires first and does not assign a blank value 13 | // though user could provide blank sample data 14 | setValue(instance,propertyName,'x') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/ConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.buildtestdata.builders.DataBuilderContext 4 | import grails.gorm.validation.ConstrainedProperty 5 | import grails.gorm.validation.Constraint 6 | import groovy.transform.CompileStatic 7 | 8 | @CompileStatic 9 | interface ConstraintHandler { 10 | void handle(Object instance, String propertyName, Constraint appliedConstraint, 11 | ConstrainedProperty constrainedProperty, DataBuilderContext ctx) 12 | } 13 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/ConstraintHandlerException.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class ConstraintHandlerException extends Exception { 7 | ConstraintHandlerException(String message) { 8 | super(message) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/CreditCardConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class CreditCardConstraintHandler extends AbstractHandler{ 7 | 8 | @Override 9 | void handle(Object instance, String propertyName) { 10 | setValue(instance,propertyName,'378282246310005') 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/EmailConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class EmailConstraintHandler extends AbstractHandler { 7 | 8 | @Override 9 | void handle(Object instance, String propertyName) { 10 | setValue(instance,propertyName,'a@b.com') 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/ExampleConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.buildtestdata.builders.DataBuilderContext 4 | import grails.buildtestdata.utils.IsoDateUtil 5 | import grails.gorm.validation.ConstrainedProperty 6 | import grails.gorm.validation.Constraint 7 | import groovy.transform.CompileDynamic 8 | import groovy.transform.CompileStatic 9 | import groovy.util.logging.Slf4j 10 | 11 | import java.time.LocalDate 12 | import java.time.LocalDateTime 13 | 14 | @Slf4j 15 | @CompileStatic 16 | class ExampleConstraintHandler extends AbstractHandler { 17 | @Override 18 | void handle(Object instance, String propertyName, Constraint appliedConstraint, 19 | ConstrainedProperty constrainedProperty, DataBuilderContext ctx) { 20 | Object exValue = getExampleContraintValue(constrainedProperty) 21 | if(exValue != null){ 22 | setValue(instance, propertyName, exValue) 23 | } 24 | } 25 | 26 | @CompileDynamic 27 | Object getExampleContraintValue(constrainedProperty){ 28 | Object exValue = constrainedProperty.metaConstraints["example"] 29 | if(exValue == null) return null 30 | 31 | if (exValue instanceof String) { 32 | String sval = exValue as String 33 | Class typeToConvertTo = constrainedProperty.propertyType 34 | 35 | if (Date.isAssignableFrom(typeToConvertTo)) { 36 | exValue = IsoDateUtil.parse(sval) 37 | } else if (LocalDate.isAssignableFrom(typeToConvertTo)) { 38 | exValue = IsoDateUtil.parseLocalDate(sval) 39 | //LocalDate.parse(val, DateTimeFormatter.ISO_DATE_TIME) 40 | } else if (LocalDateTime.isAssignableFrom(typeToConvertTo)) { 41 | exValue = IsoDateUtil.parseLocalDateTime(sval) 42 | } else if (Number.isAssignableFrom(typeToConvertTo)) { 43 | exValue = sval.asType(typeToConvertTo) 44 | } 45 | } 46 | 47 | return exValue 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/InListConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.gorm.validation.ConstrainedProperty 4 | import groovy.transform.CompileStatic 5 | 6 | @CompileStatic 7 | class InListConstraintHandler extends AbstractHandler{ 8 | 9 | @Override 10 | void handle(Object instance, String propertyName, ConstrainedProperty constrainedProperty) { 11 | setValue(instance,propertyName,constrainedProperty.inList[0]) 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/MatchesConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.gorm.validation.ConstrainedProperty 4 | import grails.gorm.validation.Constraint 5 | import groovy.transform.CompileStatic 6 | import nl.flotsam.xeger.Xeger 7 | import org.grails.datastore.gorm.validation.constraints.MatchesConstraint 8 | import org.grails.datastore.mapping.validation.ValidationErrors 9 | 10 | @CompileStatic 11 | class MatchesConstraintHandler extends AbstractHandler { 12 | 13 | @Override 14 | void handle(Object instance, String propertyName, Constraint appliedConstraint, ConstrainedProperty constrainedProperty) { 15 | // If what we have already matches, we are good 16 | if(!appliedConstraint.validate(instance,getValue(instance,propertyName),new ValidationErrors(instance))){ 17 | MatchesConstraint matchesConstraint = appliedConstraint as MatchesConstraint 18 | Xeger generator = new Xeger(matchesConstraint.regex) 19 | setValue(instance,propertyName,generator.generate()) 20 | } 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/MaxConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.gorm.validation.ConstrainedProperty 4 | import groovy.transform.CompileStatic 5 | 6 | @CompileStatic 7 | class MaxConstraintHandler extends AbstractHandler { 8 | 9 | @Override 10 | void handle(Object instance, String propertyName, ConstrainedProperty constrainedProperty) { 11 | setValue(instance,propertyName,constrainedProperty.max) 12 | } 13 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/MaxSizeConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.buildtestdata.builders.DataBuilderContext 4 | import grails.gorm.validation.ConstrainedProperty 5 | import grails.gorm.validation.Constraint 6 | import groovy.transform.CompileStatic 7 | 8 | @CompileStatic 9 | class MaxSizeConstraintHandler extends AbstractHandler { 10 | 11 | @Override 12 | void handle(Object instance, String propertyName, Constraint appliedConstraint, 13 | ConstrainedProperty constrainedProperty, DataBuilderContext ctx) { 14 | pad(instance, propertyName, constrainedProperty, ctx,constrainedProperty.getMaxSize()) 15 | } 16 | 17 | void pad(Object instance, String propertyName, ConstrainedProperty constrainedProperty, DataBuilderContext ctx, int maxSize) { 18 | def value = getValue(instance, propertyName) 19 | if (value instanceof Collection && value.size() > maxSize) { 20 | setValue(instance, propertyName, value.drop(value.size() - maxSize)) 21 | } else if (value?.respondsTo('size')) { 22 | Integer size = value.invokeMethod('size', null) as Integer 23 | if (size > maxSize) { 24 | Range range = (0..maxSize - 1) 25 | setValue(instance, propertyName, value.invokeMethod('getAt', range)) 26 | } 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/MinConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.gorm.validation.ConstrainedProperty 4 | import groovy.transform.CompileStatic 5 | 6 | @CompileStatic 7 | class MinConstraintHandler extends AbstractHandler{ 8 | 9 | @Override 10 | void handle(Object instance, String propertyName, ConstrainedProperty constrainedProperty) { 11 | setValue(instance,propertyName,constrainedProperty.min) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/MinSizeConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.buildtestdata.builders.DataBuilderContext 4 | import grails.gorm.validation.ConstrainedProperty 5 | import grails.gorm.validation.Constraint 6 | import groovy.transform.CompileStatic 7 | 8 | @CompileStatic 9 | class MinSizeConstraintHandler extends AbstractHandler{ 10 | @Override 11 | void handle(Object instance, String propertyName, Constraint appliedConstraint, 12 | ConstrainedProperty constrainedProperty, DataBuilderContext ctx) { 13 | 14 | Object propertyValue = getValue(instance, propertyName) 15 | handle(instance, propertyName, constrainedProperty, ctx, constrainedProperty.minSize, propertyValue) 16 | } 17 | 18 | void handle(Object domain, String propertyName, ConstrainedProperty constrained, DataBuilderContext ctx, 19 | int minSize, Object propertyValue) { 20 | if(String.isAssignableFrom(String)){ 21 | String stringValue = (propertyValue ?: '') as String 22 | // Try not to mangle email addresses and urls 23 | if (constrained.hasAppliedConstraint(ConstrainedProperty.URL_CONSTRAINT)) { 24 | setValue(domain, propertyName, "http://${'a'.padRight(minSize - 11, 'a')}.com") 25 | } 26 | else if (constrained.hasAppliedConstraint(ConstrainedProperty.EMAIL_CONSTRAINT)) { 27 | setValue(domain, propertyName, stringValue.padLeft(minSize, 'a')) 28 | } 29 | else { 30 | setValue(domain, propertyName, stringValue.padRight(minSize, '.')) 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/NullableConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.buildtestdata.builders.DataBuilderContext 4 | import grails.buildtestdata.utils.Basics 5 | import grails.gorm.validation.ConstrainedProperty 6 | import grails.gorm.validation.Constraint 7 | import groovy.transform.CompileStatic 8 | import groovy.util.logging.Slf4j 9 | 10 | @Slf4j 11 | @CompileStatic 12 | class NullableConstraintHandler extends AbstractHandler { 13 | 14 | @Override 15 | void handle(Object instance, String propertyName, Constraint appliedConstraint, ConstrainedProperty constrainedProperty, DataBuilderContext ctx) { 16 | Object value = determineBasicValue(propertyName, constrainedProperty) 17 | if (value == null) { 18 | value = determineNonStandardValue(instance, propertyName, appliedConstraint, constrainedProperty, ctx) 19 | } 20 | 21 | setValue(instance, propertyName, value) 22 | } 23 | 24 | Object determineNonStandardValue(Object instance, String propertyName, Constraint appliedConstraint, ConstrainedProperty constrainedProperty, DataBuilderContext ctx) { 25 | ctx.satisfyNested(instance, propertyName, constrainedProperty.propertyType) 26 | } 27 | 28 | Object determineBasicValue(String propertyName, ConstrainedProperty constrainedProperty) { 29 | switch (constrainedProperty.propertyType) { 30 | case String: return propertyName 31 | default: return Basics.getBasicValue(constrainedProperty.propertyType) 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/PersistentEntityNullableConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.buildtestdata.builders.DataBuilderContext 4 | import grails.gorm.validation.ConstrainedProperty 5 | import grails.gorm.validation.Constraint 6 | import groovy.transform.CompileStatic 7 | import org.grails.datastore.gorm.GormEntity 8 | import org.grails.datastore.mapping.model.MappingContext 9 | import org.grails.datastore.mapping.model.PersistentEntity 10 | import org.grails.datastore.mapping.model.PersistentProperty 11 | import org.grails.datastore.mapping.model.types.Basic 12 | import org.grails.datastore.mapping.model.types.ManyToOne 13 | import org.grails.datastore.mapping.model.types.ToMany 14 | 15 | @CompileStatic 16 | class PersistentEntityNullableConstraintHandler extends NullableConstraintHandler { 17 | PersistentEntity persistentEntity 18 | MappingContext mappingContext 19 | 20 | PersistentEntityNullableConstraintHandler(PersistentEntity persistentEntity, MappingContext mappingContext) { 21 | this.persistentEntity = persistentEntity 22 | this.mappingContext = mappingContext 23 | } 24 | 25 | @Override 26 | void setValue(Object instance, String propertyName, Object value) { 27 | PersistentProperty domainProp = persistentEntity.getPropertyByName(propertyName) 28 | 29 | if (value && domainProp instanceof ManyToOne) { 30 | ManyToOne toOneProp = domainProp as ManyToOne 31 | GormEntity owningObject = (GormEntity) value 32 | owningObject.addTo(toOneProp.referencedPropertyName, instance) 33 | } 34 | else if (domainProp instanceof ToMany) { 35 | ((GormEntity) instance).addTo(propertyName, value) 36 | } 37 | else { 38 | super.setValue(instance, propertyName, value) 39 | } 40 | } 41 | 42 | @Override 43 | Object determineNonStandardValue(Object instance, String propertyName, Constraint appliedConstraint, 44 | ConstrainedProperty constrainedProperty, DataBuilderContext ctx) { 45 | 46 | PersistentProperty domainProp = persistentEntity.getPropertyByName(propertyName) 47 | 48 | // Use the associatedEntity class and return a collection with the build object if its a ToMany 49 | if (domainProp instanceof ToMany) { 50 | ToMany toManyProp = domainProp as ToMany 51 | 52 | Class referencedClass 53 | if (toManyProp.basic) { 54 | referencedClass = ((Basic)toManyProp).componentType 55 | } 56 | else { 57 | referencedClass = toManyProp.associatedEntity?.javaClass 58 | } 59 | 60 | def obj = ctx.satisfyNested(instance, propertyName, referencedClass) 61 | return [obj] 62 | } 63 | // Set save=true on this build so we don't get transient exception in integration tests, works fine in units 64 | // org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation 65 | else if (domainProp instanceof ManyToOne) { 66 | return ctx.satisfyNested(instance, propertyName, constrainedProperty.propertyType, true) 67 | } 68 | 69 | return ctx.satisfyNested(instance, propertyName, constrainedProperty.propertyType) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/RangeConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.gorm.validation.ConstrainedProperty 4 | import groovy.transform.CompileStatic 5 | 6 | @CompileStatic 7 | class RangeConstraintHandler extends AbstractHandler { 8 | 9 | @Override 10 | void handle(Object instance, String propertyName, ConstrainedProperty constrainedProperty) { 11 | setValue(instance,propertyName,constrainedProperty.range.from) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/SizeConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.buildtestdata.builders.DataBuilderContext 4 | import grails.gorm.validation.ConstrainedProperty 5 | import grails.gorm.validation.Constraint 6 | import groovy.transform.CompileStatic 7 | 8 | @CompileStatic 9 | class SizeConstraintHandler extends AbstractHandler { 10 | 11 | MinSizeConstraintHandler minSizeConstraintHandler = new MinSizeConstraintHandler() 12 | MaxSizeConstraintHandler maxSizeConstraintHandler = new MaxSizeConstraintHandler() 13 | 14 | @Override 15 | void handle(Object instance, String propertyName, Constraint appliedConstraint, 16 | ConstrainedProperty constrainedProperty, DataBuilderContext ctx) { 17 | handle(instance,propertyName,constrainedProperty,ctx) 18 | } 19 | 20 | void handle(Object instance, String propertyName, ConstrainedProperty constrainedProperty, DataBuilderContext ctx) { 21 | 22 | Object propertyValue = getValue(instance, propertyName) 23 | minSizeConstraintHandler.handle(instance, propertyName, constrainedProperty, ctx, constrainedProperty.minSize, propertyValue) 24 | 25 | maxSizeConstraintHandler.pad( 26 | instance, propertyName, constrainedProperty, ctx, constrainedProperty.size.max() as int 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/UniqueConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.gorm.validation.ConstrainedProperty 4 | import groovy.transform.CompileStatic 5 | 6 | @CompileStatic 7 | class UniqueConstraintHandler extends AbstractHandler{ 8 | 9 | @Override 10 | void handle(Object instance, String propertyName, ConstrainedProperty constrainedProperty) { 11 | // unique isn't supported, if the value we've got in there isn't valid by this point, throw an error letting 12 | // the user know why we're not passing 13 | // if (constrainedProperty.unique && !constrainedProperty?.validate(domain, domain."$propertyName", new ValidationErrors(this))) { 14 | // String error = "unique constraint support not implemented: property $propertyName of ${domain.class.name}" 15 | // throw new ConstraintHandlerException(error) 16 | // } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/UrlConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class UrlConstraintHandler extends AbstractHandler { 7 | 8 | @Override 9 | void handle(Object instance, String propertyName) { 10 | setValue(instance,propertyName,'http://www.example.com') 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/handler/ValidatorConstraintHandler.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.handler 2 | 3 | import grails.gorm.validation.ConstrainedProperty 4 | import grails.gorm.validation.Constraint 5 | import groovy.transform.CompileStatic 6 | import org.grails.datastore.mapping.validation.ValidationErrors 7 | 8 | @CompileStatic 9 | class ValidatorConstraintHandler extends AbstractHandler{ 10 | 11 | @Override 12 | void handle(Object instance, String propertyName, Constraint appliedConstraint, ConstrainedProperty constrainedProperty) { 13 | // validate isn't supported, if the value we've got in there isn't valid by this point, throw an error letting 14 | // the user know why we're not passing 15 | if ( !appliedConstraint?.validate(instance, getValue(instance,propertyName), new ValidationErrors(instance)) ) { 16 | String error = "Validator constraint support not implemented in build-test-data, attempted value " + 17 | "(${getValue(instance,propertyName)}) does not pass validation: property $propertyName of ${instance.class.name}" 18 | throw new ConstraintHandlerException(error) 19 | } 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/mixin/Build.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.mixin 2 | 3 | import java.lang.annotation.Retention 4 | import java.lang.annotation.RetentionPolicy 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | @interface Build { 8 | Class[] value() 9 | } 10 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/propsresolver/ClosurePropsResolver.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.propsresolver 2 | 3 | class ClosurePropsResolver implements InitialPropsResolver{ 4 | 5 | Map initialProps 6 | 7 | ClosurePropsResolver(Map initialProps){ 8 | this.initialProps=initialProps 9 | } 10 | 11 | @Override 12 | Map getInitialProps(Class target) { 13 | Closure c = initialProps.get(target) 14 | 15 | return c? c() : null 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/propsresolver/InitialPropsResolver.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.propsresolver 2 | 3 | interface InitialPropsResolver { 4 | Map getInitialProps(Class target) 5 | 6 | static class EmptyInitialPropsResolver implements InitialPropsResolver{ 7 | @Override 8 | Map getInitialProps(Class target) { 9 | return null 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/propsresolver/MapPropsResolver.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.propsresolver 2 | 3 | class MapPropsResolver implements InitialPropsResolver{ 4 | Map data 5 | MapPropsResolver(Map data){ 6 | this.data=data 7 | } 8 | @Override 9 | Map getInitialProps(Class target) { 10 | return data.get(target) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/testing/DependencyDataTest.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.testing 2 | 3 | import grails.buildtestdata.TestDataConfigurationHolder 4 | import grails.buildtestdata.utils.DomainUtil 5 | import grails.buildtestdata.TestData 6 | import grails.buildtestdata.builders.PersistentEntityDataBuilder 7 | import grails.testing.gorm.DataTest 8 | import groovy.transform.CompileStatic 9 | 10 | /** 11 | * Overrides the mockDomains on DataTest to mock the dependency tree 12 | */ 13 | @CompileStatic 14 | trait DependencyDataTest extends DataTest { 15 | /** 16 | * Empty, override in traits or the implemented tests. 17 | * Gets called/fired for all the mocked domain during mockDependencyGraph 18 | * 19 | * @param entityClasses 20 | */ 21 | void onMockDomains(Class... entityClasses) { } 22 | 23 | @Override 24 | void mockDomains(Class... domainClassesToMock){ 25 | mockDependencyGraph([] as Set, DomainUtil.expandSubclasses(domainClassesToMock)) 26 | } 27 | 28 | void mockDependencyGraph(Set mockedList, Class... domainClassesToMock) { 29 | // Resolve any additional build classes for this mock list 30 | domainClassesToMock = resolveAdditionalBuild(domainClassesToMock) 31 | 32 | // First mock these domains so they are registered with Grails 33 | DataTest.super.mockDomains(domainClassesToMock) 34 | 35 | // Call the next Trait in the chain 36 | onMockDomains(domainClassesToMock) 37 | 38 | mockedList.addAll(domainClassesToMock) 39 | 40 | Set requiredClasses = domainClassesToMock.collectMany { Class clazz -> 41 | // For domain instance building, we only care about concrete classes 42 | if (!DomainUtil.isAbstract(clazz)) { 43 | PersistentEntityDataBuilder builder = (PersistentEntityDataBuilder) TestData.findBuilder(clazz) 44 | //println "requiredDomainClasses ${builder.requiredDomainClasses}" 45 | builder.requiredDomainClasses 46 | } 47 | else { 48 | [] 49 | } 50 | } as Set 51 | 52 | // Remove any that we've already seen 53 | requiredClasses.removeAll(mockedList) 54 | if (requiredClasses) { 55 | mockDependencyGraph(mockedList, requiredClasses as Class[]) 56 | } 57 | } 58 | 59 | private Set resolveAdditionalBuild(Class... domainClassesToMock) { 60 | Set allClasses = domainClassesToMock as Set 61 | allClasses.addAll(domainClassesToMock.collectMany { TestDataConfigurationHolder.getUnitAdditionalBuildFor(it.name) }) 62 | if (allClasses.size() > domainClassesToMock.size()) { 63 | return resolveAdditionalBuild(allClasses as Class[]) 64 | } 65 | else { 66 | allClasses 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/utils/DomainUtil.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.utils 2 | 3 | import grails.buildtestdata.TestDataConfigurationHolder 4 | import groovy.transform.CompileStatic 5 | import org.grails.datastore.mapping.model.PersistentEntity 6 | import org.grails.datastore.mapping.reflect.ClassPropertyFetcher 7 | 8 | import java.lang.reflect.Modifier 9 | 10 | @CompileStatic 11 | class DomainUtil { 12 | static PersistentEntity getPersistentEntity(Class clazz) { 13 | ClassPropertyFetcher.getStaticPropertyValue(clazz, "gormPersistentEntity", PersistentEntity) 14 | } 15 | 16 | /** 17 | * check the test config for info on what conrete classes to sub in for abstracts 18 | */ 19 | static Class findConcreteSubclass(Class abstractClass) { 20 | Class concreteClass = abstractClass 21 | if (isAbstract(abstractClass)) { 22 | concreteClass = TestDataConfigurationHolder.getAbstractDefaultFor(abstractClass.name) 23 | if (!concreteClass) { 24 | throw new IllegalArgumentException("No concrete subclass found for $abstractClass, use 'testDataConfig.abstractDefault' to configure") 25 | } 26 | } 27 | concreteClass 28 | } 29 | 30 | static boolean isAbstract(Class clazz) { 31 | Modifier.isAbstract(clazz.getModifiers()) 32 | } 33 | 34 | static Class[] expandSubclasses(Class... classes) { 35 | classes.collectMany { Class clazz -> 36 | List result = [clazz] 37 | Class subClass = findConcreteSubclass(clazz) 38 | if (subClass != clazz) { 39 | result << subClass 40 | } 41 | result 42 | } as Class[] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/grails/buildtestdata/utils/MetaHelper.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata.utils 2 | 3 | import grails.buildtestdata.TestData 4 | 5 | class MetaHelper { 6 | static void addBuildMetaMethods(Class... entityClasses) { 7 | entityClasses.each { Class ec -> 8 | def mc = ec.metaClass 9 | mc.static.build = {-> 10 | TestData.build(ec) 11 | } 12 | mc.static.build = { Map args -> 13 | TestData.build(args, ec) 14 | } 15 | mc.static.build = { Map args, Map data -> 16 | TestData.build(args, ec, data) 17 | } 18 | mc.static.findOrBuild = {-> 19 | TestData.findOrBuild(ec, [:]) 20 | } 21 | mc.static.findOrBuild = { Map data -> 22 | TestData.findOrBuild(ec, data) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plugin/src/main/resources/idea/buildTestData.gdsl: -------------------------------------------------------------------------------- 1 | def ctx = context(ctype: "org.grails.datastore.gorm.GormEntity") 2 | contributor(ctx) { 3 | // Protect against null class type in some cases 4 | if (classType) { 5 | // Pull all fields as parameters 6 | def p = classType.fields?.collect { 7 | parameter(name: it.name, type: findClass(it.type.presentableText)) 8 | } 9 | 10 | // Add methods 11 | def buildTestMethods = ["build", "findOrBuild"] 12 | buildTestMethods.each { 13 | method(name: it, isStatic: true, params: [args: p], type: classType.qualifiedName) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/basetests/BelongsToSpec.groovy: -------------------------------------------------------------------------------- 1 | package basetests 2 | 3 | import grails.buildtestdata.BuildDomainTest 4 | import spock.lang.Specification 5 | 6 | class BelongsToSpec extends Specification implements BuildDomainTest { 7 | 8 | void "Book with BelongsTo"() { 9 | when: 10 | def entity = BookBelongsTo.build() 11 | 12 | then: 13 | assert entity 14 | assert entity.id 15 | assert entity.author.name 16 | } 17 | 18 | void "ext works"() { 19 | when: 20 | mockDomain(AuthorWithExt) 21 | def entity = AuthorWithExt.build() 22 | 23 | then: 24 | assert entity 25 | assert entity.id 26 | assert entity.ext.author 27 | } 28 | 29 | } 30 | 31 | @grails.persistence.Entity 32 | class AuthorHasBook { 33 | String name 34 | } 35 | 36 | @grails.persistence.Entity 37 | class BookBelongsTo { 38 | static belongsTo = [author: AuthorHasBook] 39 | } 40 | 41 | @grails.persistence.Entity 42 | class AuthorWithExt { 43 | AuthorExt ext 44 | } 45 | 46 | @grails.persistence.Entity 47 | class AuthorExt { 48 | static belongsTo = [author: AuthorWithExt] 49 | } -------------------------------------------------------------------------------- /plugin/src/test/groovy/basetests/BooleanTests.groovy: -------------------------------------------------------------------------------- 1 | package basetests 2 | 3 | import spock.lang.Specification 4 | 5 | class BooleanTests extends Specification implements DomainTestBase { 6 | 7 | void testBooleanDefaultGroovyTruthFalseOk() { 8 | when: 9 | def domainClass = createDomainClass(""" 10 | @grails.persistence.Entity 11 | class TestDomain { 12 | Long id 13 | Long version 14 | Boolean testProperty 15 | } 16 | """) 17 | 18 | def domainObject = domainClass.build() 19 | 20 | then: 21 | assert domainObject != null 22 | assert domainObject.testProperty == false 23 | } 24 | 25 | void testBooleanManuallySetValues() { 26 | when: 27 | def domainClass = createDomainClass(""" 28 | @grails.persistence.Entity 29 | class TestDomain2 { 30 | Long id 31 | Long version 32 | Boolean testProperty 33 | } 34 | """) 35 | 36 | def domainObject = domainClass.build(testProperty: true) 37 | 38 | then: 39 | assert domainObject != null 40 | assert domainObject.testProperty == true 41 | 42 | when: 43 | domainObject = domainClass.build(testProperty: false) 44 | 45 | then: 46 | assert domainObject != null 47 | assert domainObject.testProperty == false 48 | 49 | } 50 | 51 | void testBooleanNullable() { 52 | expect: 53 | def domainClass = createDomainClass(""" 54 | @grails.persistence.Entity 55 | class TestDomain3 { 56 | Long id 57 | Long version 58 | Boolean testProperty 59 | static constraints = { 60 | testProperty(nullable: true) 61 | } 62 | } 63 | """) 64 | 65 | def domainObject = domainClass.build() 66 | 67 | assert domainObject != null 68 | assert domainObject.testProperty == null 69 | } 70 | 71 | void testBooleanNotNullable() { 72 | expect: 73 | def domainClass = createDomainClass(""" 74 | @grails.persistence.Entity 75 | class TestDomain4 { 76 | Long id 77 | Long version 78 | Boolean testProperty 79 | } 80 | """) 81 | 82 | def domainObject = domainClass.build() 83 | 84 | assert domainObject != null 85 | assert domainObject.testProperty != null 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/basetests/ByteTests.groovy: -------------------------------------------------------------------------------- 1 | package basetests 2 | 3 | import spock.lang.Specification 4 | 5 | class ByteTests extends Specification implements DomainTestBase { 6 | 7 | void testByteNotNull() { 8 | expect: 9 | def domainClass = createDomainClass(""" 10 | @grails.persistence.Entity 11 | class TestByteNotNullDomain { 12 | Long id 13 | Long version 14 | Byte[] testByteObject 15 | byte[] testBytePrimitive 16 | } 17 | """) 18 | 19 | def domainObject = domainClass.build() 20 | 21 | assert domainObject != null 22 | assert domainObject.testByteObject != null 23 | assert domainObject.testBytePrimitive != null 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/basetests/DomainTestBase.groovy: -------------------------------------------------------------------------------- 1 | package basetests 2 | 3 | import grails.buildtestdata.BuildDataTest 4 | 5 | trait DomainTestBase extends BuildDataTest { 6 | 7 | Class createDomainClass(String classText) { 8 | Class domainClass = new GroovyClassLoader().parseClass(classText) 9 | mockDomain(domainClass) 10 | domainClass 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/basetests/ExampleDataSpec.groovy: -------------------------------------------------------------------------------- 1 | package basetests 2 | 3 | import spock.lang.Shared 4 | import spock.lang.Specification 5 | 6 | import java.text.SimpleDateFormat 7 | import java.time.LocalDate 8 | 9 | class ExampleDataSpec extends Specification implements DomainTestBase { 10 | static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") 11 | 12 | void setupSpec(){ 13 | sdf.setTimeZone(TimeZone.getTimeZone('UTC')) 14 | } 15 | 16 | void "test uses example if its there"() { 17 | when: 18 | mockDomain(ExampleDataDom) 19 | ExampleDataDom entity = ExampleDataDom.build() 20 | 21 | then: 22 | entity.name == 'name' 23 | entity.nameNull == null 24 | entity.stringEx == 'Bill' 25 | entity.longEx == 99 26 | entity.bigdEx == 42.42 27 | sdf.format(entity.dateEx).substring(0,10) == sdf.format(new Date()).substring(0,10) 28 | entity.localDateEx.toString() == '2018-01-09' 29 | 30 | entity.bigdExFromStr == 42.42 31 | sdf.format(entity.dateExFromStr).startsWith('2013-11-01T23:00:00') 32 | entity.locDateFromStr.toString() == '2018-01-09' 33 | } 34 | 35 | } 36 | 37 | @grails.persistence.Entity 38 | class ExampleDataDom { 39 | String name 40 | String nameNull 41 | String stringEx 42 | Long longEx 43 | Date dateEx 44 | LocalDate localDateEx 45 | BigDecimal bigdEx 46 | 47 | Date dateExFromStr 48 | LocalDate locDateFromStr 49 | BigDecimal bigdExFromStr 50 | 51 | static constraints = { 52 | nameNull example: 'no go', nullable: true 53 | stringEx example: 'Bill' 54 | longEx example: 99 55 | bigdEx example: 42.42 56 | dateEx example: new Date() 57 | localDateEx example: LocalDate.parse('2018-01-09') 58 | 59 | bigdExFromStr example: '42.42' 60 | dateExFromStr example: '2013-11-01T23:00:00Z' 61 | locDateFromStr example: '2018-01-09' 62 | } 63 | } -------------------------------------------------------------------------------- /plugin/src/test/groovy/basetests/HasManyMinSizeSpec.groovy: -------------------------------------------------------------------------------- 1 | package basetests 2 | 3 | import grails.buildtestdata.BuildDomainTest 4 | import spock.lang.Specification 5 | 6 | class HasManyMinSizeSpec extends Specification implements BuildDomainTest { 7 | 8 | void testHasManyNullableFalse() { 9 | when: 10 | def entity = AuthorHasManyMin.build() 11 | 12 | then: 13 | assert entity 14 | assert entity.id 15 | assert entity.books 16 | assert 2 == entity.books.size() 17 | } 18 | 19 | } 20 | 21 | @grails.persistence.Entity 22 | class AuthorHasManyMin { 23 | static hasMany = [books: BookToOneMin] 24 | 25 | static constraints = { 26 | books nullable: false, minSize: 2 27 | } 28 | } 29 | 30 | @grails.persistence.Entity 31 | class BookToOneMin { 32 | static belongsTo = [author: AuthorHasManyMin] 33 | } 34 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/basetests/HasManySpec.groovy: -------------------------------------------------------------------------------- 1 | package basetests 2 | 3 | import grails.buildtestdata.BuildDomainTest 4 | import spock.lang.Specification 5 | 6 | class HasManySpec extends Specification implements BuildDomainTest { 7 | 8 | void testHasManyNullableFalse() { 9 | when: 10 | def entity = AuthorHasMany.build() 11 | 12 | then: 13 | assert entity 14 | assert entity.id 15 | assert entity.books 16 | assert 1 == entity.books.size() 17 | } 18 | 19 | } 20 | 21 | @grails.persistence.Entity 22 | class AuthorHasMany { 23 | //String firstName 24 | static hasMany = [books: BookToOne] 25 | 26 | static constraints = { 27 | books nullable: false 28 | } 29 | } 30 | 31 | @grails.persistence.Entity 32 | class BookToOne { 33 | //String title 34 | static belongsTo = [author: AuthorHasMany] 35 | } 36 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/basetests/IncludesSpec.groovy: -------------------------------------------------------------------------------- 1 | package basetests 2 | 3 | import grails.buildtestdata.TestData 4 | import grails.buildtestdata.BuildDomainTest 5 | import grails.buildtestdata.builders.DataBuilderContext 6 | import spock.lang.Specification 7 | 8 | class IncludesSpec extends Specification implements BuildDomainTest { 9 | 10 | void "sanity check optional is null"() { 11 | expect: 12 | domain.mustHave 13 | domain.optional == null 14 | } 15 | 16 | void "test include fields will throw exception"() { 17 | when: 18 | def ent = IncludeDom.build(includes: ['optional', 'optExt']) 19 | 20 | then: "its not mocked automatically since its not required" 21 | thrown RuntimeException 22 | } 23 | 24 | void "test getRequiredFields has includes fields"() { 25 | when: 26 | mockDomains(IncludeDomExt) 27 | def ent = IncludeDom.build(includes: ['optional', 'optExt']) 28 | DataBuilderContext ctx = new DataBuilderContext() 29 | ctx.includes = ['optional', 'optExt'] 30 | Set incl = TestData.findBuilder(IncludeDom).getFieldsToBuild(ctx) 31 | 32 | then: 33 | incl.containsAll(['mustHave','optional', 'optExt']) 34 | } 35 | 36 | void "test getRequiredFields has all when include is '*"() { 37 | when: 38 | DataBuilderContext ctx = new DataBuilderContext() 39 | ctx.includes = '*' 40 | Set incl = TestData.findBuilder(IncludeDom).getFieldsToBuild(ctx) 41 | 42 | then: 43 | incl.containsAll(['mustHave','optional', 'optExt', 'amount']) 44 | } 45 | 46 | void "test include list works"() { 47 | when: 48 | def ent = IncludeDom.build(includes: ['optional']) 49 | 50 | then: 51 | ent.mustHave 52 | ent.optional 53 | ent.optExt == null 54 | } 55 | 56 | void "test include fields works with '*' for include all"() { 57 | when: 58 | def ent = IncludeDom.build(includes: '*') 59 | 60 | then: 61 | ent.mustHave 62 | ent.optional 63 | ent.optExt.title 64 | } 65 | 66 | } 67 | 68 | @grails.persistence.Entity 69 | class IncludeDom { 70 | BigDecimal amount 71 | String mustHave 72 | String optional 73 | IncludeDomExt optExt 74 | static constraints = { 75 | optional nullable: true 76 | optExt nullable: true 77 | amount nullable: true 78 | } 79 | } 80 | 81 | @grails.persistence.Entity 82 | class IncludeDomExt { 83 | String title 84 | static belongsTo = [inc: IncludeDom] 85 | } 86 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/basetests/JSR310Tests.groovy: -------------------------------------------------------------------------------- 1 | package basetests 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class JSR310Tests extends Specification implements DomainTestBase { 7 | @Unroll 8 | void "create non-nullable property type #typeName"(String typeName) { 9 | when: 10 | def domainClass = createDomainClass(""" 11 | @grails.persistence.Entity 12 | class TestJava$typeName { 13 | Long id 14 | Long version 15 | java.time.$typeName testProperty 16 | 17 | static constraints = { 18 | testProperty(nullable: false) 19 | } 20 | } 21 | """) 22 | 23 | def domainObject = domainClass.build() 24 | 25 | then: 26 | domainObject != null 27 | domainObject.testProperty != null 28 | 29 | where: 30 | typeName << ['LocalDateTime', 'LocalDate', 'LocalTime'] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/grails/buildtestdata/BuildDomainTestSpec.groovy: -------------------------------------------------------------------------------- 1 | package grails.buildtestdata 2 | 3 | import spock.lang.Shared 4 | import spock.lang.Specification 5 | 6 | class BuildDomainTestSpec extends Specification implements BuildDomainTest{ 7 | @Shared int id 8 | 9 | void "test basic persistence mocking"() { 10 | setup: 11 | build().save() 12 | build().save() 13 | 14 | expect: 15 | SampleUnitTestDomain.count() == 2 16 | SampleUnitTestDomainChild.count() == 2 17 | } 18 | 19 | void "test domain instance"() { 20 | setup: 21 | id = System.identityHashCode(domain) 22 | 23 | expect: 24 | domain != null 25 | domain.hashCode() == id 26 | domain.name != null 27 | 28 | when: 29 | domain.name = 'Robert' 30 | 31 | then: 32 | domain.name == 'Robert' 33 | } 34 | 35 | void "test props"() { 36 | when: 37 | def u = build(name: 'bill') 38 | entity.name = 'bob' 39 | 40 | then: 41 | u.name == 'bill' 42 | entity.name == 'bob' 43 | } 44 | 45 | void "test we get a new domain each time"() { 46 | expect: 47 | entity != null 48 | entity.name == 'name' 49 | entity.child 50 | System.identityHashCode(entity) != id 51 | } 52 | 53 | } 54 | 55 | @grails.persistence.Entity 56 | class SampleUnitTestDomain { 57 | String name 58 | SampleUnitTestDomainChild child 59 | } 60 | 61 | @grails.persistence.Entity 62 | class SampleUnitTestDomainChild { 63 | String name 64 | } 65 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/hibernate/domains/Bar.groovy: -------------------------------------------------------------------------------- 1 | package hibernate.domains 2 | 3 | @grails.persistence.Entity 4 | class Bar { 5 | String name 6 | } 7 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/hibernate/domains/Foo.groovy: -------------------------------------------------------------------------------- 1 | package hibernate.domains 2 | 3 | @grails.persistence.Entity 4 | class Foo { 5 | String name 6 | Bar bar 7 | } -------------------------------------------------------------------------------- /plugin/src/test/groovy/hibernate/specs/HibDomainClassSpec.groovy: -------------------------------------------------------------------------------- 1 | package hibernate.specs 2 | 3 | import grails.buildtestdata.BuildHibernateTest 4 | import grails.test.hibernate.HibernateSpec 5 | import hibernate.domains.Bar 6 | import hibernate.domains.Foo 7 | import spock.lang.Stepwise 8 | 9 | import static grails.buildtestdata.TestData.* 10 | 11 | @Stepwise 12 | class HibDomainClassSpec extends HibernateSpec implements BuildHibernateTest { 13 | 14 | // Dont' need to give it Bar as it will see its an association and mock it 15 | List getDomainClasses() { [Foo] } 16 | 17 | void setupSpec() { 18 | //build a Bar here to test buildLazy 19 | Bar.withTransaction { 20 | build(Bar) 21 | } 22 | } 23 | 24 | void "test that Bar got mocked and saved"() { 25 | 26 | when: "buildLazy should pick up the one thats already there from setupSpec" 27 | def bar = build(Bar, find: true) //should pick up the one thats already there 28 | 29 | then: "the id should be 1 as it was saved already" 30 | bar.id == 1 31 | bar.name 32 | } 33 | 34 | void "test foo and its bar association"() { 35 | build(Foo, [name: 'bill']) 36 | def foo = Foo.findById(1) //this is the first insert of Foo so 1 should be there 37 | 38 | expect: 39 | foo.id == 1 40 | foo.name == 'bill' 41 | foo.bar.name == 'name' 42 | foo.bar.id == 2 43 | 44 | } 45 | 46 | void "round 2"() { 47 | when: "buildLazy is called but will create a new one" 48 | def foo = findOrBuild(Foo) 49 | //build(Foo) 50 | 51 | then: "a new Foo was saved since the build was called in another test and not in setupSpec" 52 | foo.id == 2 53 | foo.bar.id == 3 54 | 55 | } 56 | 57 | void "test that Foo gets the build meta methods automatically"() { 58 | when: 59 | def foo = Foo.build() 60 | 61 | then: 62 | foo 63 | foo.id 64 | foo.name 65 | } 66 | 67 | void "test that Foo can use hibernate specific functionality"() { 68 | given: 69 | Foo.build(flush: true, name: 'Arrow', bar: build(save: false, Bar, name: 'TheBar')) 70 | 71 | when: 72 | Foo foo = Foo.createCriteria().get { 73 | createAlias 'bar', 'b' 74 | eq 'b.name', 'TheBar' 75 | } 76 | 77 | then: 78 | foo 79 | foo.bar.name == "TheBar" 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/hibernate/specs/HibernateSpecSpec.groovy: -------------------------------------------------------------------------------- 1 | package hibernate.specs 2 | 3 | import grails.test.hibernate.HibernateSpec 4 | import static grails.buildtestdata.TestData.build 5 | 6 | class HibernateSpecSpec extends HibernateSpec { 7 | 8 | //HibernateSpec scans the package the test sits in. It will automatically find the Baz 9 | void "test hibernate spec with automatic scan"() { 10 | when: 11 | build(Baz) 12 | 13 | then: 14 | def baz1 = Baz.findById(1) 15 | baz1.name 16 | baz1.nullName == null 17 | //baz1.nameWithExample == 'shibbeldy' 18 | } 19 | } 20 | 21 | @grails.persistence.Entity 22 | class Baz { 23 | String name 24 | String nullName 25 | String nameWithExample 26 | 27 | static constraints = { 28 | nullName nullable: true 29 | //nameWithExample example: 'shibbeldy' 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'build-test-data-root' 2 | 3 | include 'plugin' 4 | include 'docs' 5 | 6 | include 'examples:bookStore' 7 | include 'examples:alternativeConfig' 8 | 9 | findProject(':plugin').name = 'build-test-data' --------------------------------------------------------------------------------