The purpose of this assignment is to introduce you to Grails, including:
- Grails 3 Framework and the use of Domains, Controllers and Views
- IntelliJ IDEA integrated development environment (IDE)
- H2 Database and dbconsole
- Writing and running unit tests
- Writing and running integration tests
You are to complete this programming assignment individually. In this assignment you will build a simple web app for a book store’s administrative back end, meaning administrators will be able to add books and authors to the book store’s database.
Step 1: Watch Introduction to Grails Videos
Point your web browser to and watch
https://www.youtube.com/watch?v=PXHxo43hn34 – Getting Started with Grails Part 1, MVC including Domain and scaffolding.
https://www.youtube.com/watch?v=qNFksvLxZNU – Getting Started with Grails Part 2 – Custom UI.
https://www.youtube.com/watch?v=oL4yVtNU31E – Getting Started with Grails Part 3, Custom G-Tags.
Watch the part 1 and part 2 videos. Later, you can watch the part 3 video. Although the video uses Grails 2 and the GGTS IDE, it is still a good introduction to developing a web app using Grails. All the code introduced in the video will run with Grails 3.
If you need more resources consult the Grails User Documentation
Please note that Grails documentation URLs always point to the latest version of Grails. As of July 11, 2019, Grails 4.0.0. This tutorial will use the latest version of Grails 3 (Grails 3.3.11 as of October 18, 2020). Currently, The URL pointing to the “latest” Grails document is directed to the latest Grails 4 documentation. Generally the Grials 3 and 4 agree but you probably want to be caution and read the Grails 3 documentation:
http://docs.grails.org/3.3.11/guide/single.html
The title of the page (appearing in the browser window tab) gives the Grails version number.
You will want to study chapter 7 about GORM and the dedicated GORM manuel:
http://grails.org/doc/latest/guide/GORM.html
http://gorm.grails.org/6.1.x/hibernate/manual/
For more details about Grails views and controllers, you will want to study chapter 8 about the web layer
http://grails.org/doc/latest/guide/theWebLayer.html
Also on the right hand side of all the documentation web pages is the Quick Reference, but you need to have a wide browser window to view the links in the Quick Reference.
The documentation is a good reference but is very technical, so you will probably find it hard to understand when you first begin programming. There are good books that give easy explanation of grails, only a few books are about Grails 3.
https://grails.org/learn.html#books
There are also a large collection of Grails Guides:
http://guides.grails.org/#/index
The Grails Apprentice guide, Creating your first Grails Application,
http://guides.grails.org/creating-your-first-grails-app/guide/index.html
is a very detailed and technical introduction to creating a Grails app.
Step 2: Install IntelliJ IDEA and Java on your home machine
The only Integrated Development Environments (IDE) that runs Grails 3 is IntelliJ IDEA. Although it is possible to develop with Grails 3 without an IDE, I recommend that your team use the IntelliJ IDEA. Then, your team and instructors can help you if there are IDE challenges.
Below is a detail instructions for IntelliJ IDEA installation.The instructions are specific to a Windows machine. If you are using Mac or Linux it might differ.
1. Locate Java Version 8 on your home machine
i. Locate Java JDK on your home machine and Download version 1.8
Grails 3 and IntelliJ IDEA will need JDK 1.7 or 1.8. You probably already have a JDK on your machine, but to insure that the deployment app is successful your Java version should match the Java on the server. Currently (as of 10/24/2017), the server is using Java openjdk version 1.8.0_111. Locate the JDK on your machine. On my windows machine, the JDK are located in
C:/Program Files/Java/
Remember this location, you will need it when you make your project in IntelliJ IDEA.
If you do not have server’s Java version (1.8), you should get and install it on your machine.
https://jdk.java.net/java-se-ri/8-MR3
Download and install the JDK, not the JRE. Any version of Java 1.8 should work. Currently (10/18/2019), I am using Java jdk 1.8.0_191.
Another option for downloading Java is to use SDK Man.
SDK Man is a command line tool for controlling the version of many Grooy and Java development software.
It is useful if you are developing apps that use different Java versions, but it is overkill for the purposes of this course. if you do use SDKMAN, note that it will make a “.sdkman” directory in the root of your user directory. In the .sdkman directory, there is a “candidate” directory. The candidate directory will contain the different software and versions of the software.
2. Installing IntelliJ IDEA Ultimate
Many of you already have IntelliJ IDEA Ultimate edition, so you can skip this step.
The Grails Developer Team recommends using IntelliJ for your development environment. IntelliJ comes in two favors: Community and Ultimate. The community version is free for anyone and can run Grails 3, but it does not have many of the convenient features that the ultimate version has. Jet Brains offers an one year academic licences for the ultimate version, and our IT has installed the ultimate version on the school lab machines. I assume that you want to download and install the ultimate version on your home machine
i. Apply for JetBrains student License
Apply for a student licence at
https://www.jetbrains.com/student/
Clicking the “Apply Now” button in the middle of the page will take you to this form
https://www.jetbrains.com/shop/eform/students
Use the “University email address” tab to apply. Be sure to use your MTU email address. Click the “Apply for Free Products” button at the bottom of the page.
You should get an email from Jet Brains. The email will be from “sales.us@jetbrains.com”, and there should be a link in the email to activate your JetBrains account.
ii. Download and Install IntelliJ IDEA Ultimate
IntelliJ IDEA download website is at
https://www.jetbrains.com/idea/
Clicking the “Download” button in the middle of the page, takes you to the “chooseYourEdition” section of the page:
https://www.jetbrains.com/idea/#chooseYourEdition
Download the “Ultimate” version. For windows this will download an installer. Run the installer as administrator. Follow the wizard, you can use all the default options.
Jet Brain has good documentation for IntelliJ IDEA. You can find installation instructions at:
https://www.jetbrains.com/help/idea/installation-guide.html
iii. Read “Getting Started with Grails 3” using IntelliJ IDEA
Read a little about IntelliJ IDEA:
https://www.jetbrains.com/help/idea/2016.1/getting-started-with-grails-3.html
The left panel of IntelliJ IDEA shows the project file directories. Also the right panel (main panel) is the editor view. IntelliJ has a bottom panel. This may be hidden, but you can open different views using the buttons along the bottom of IntelliJ IDEA. The relevant view at the bottom occurs when you run your Grails app within IntelliJ IDEA. Running the Grails app will open a “Run” view that allows you to stop and start your app and also clear and print the terminal output. There are also terminal term which is access to your system terminal for executing system commands. There is which a far right panel which you access by clicking the buttons on the right boarder of IntelliJ IDEA and gives you access to build tasks and commands.
Step 3: Make the Grails project using IntelliJ IDEA
1. Make “workspace” and Project Directories
Although IntelliJ IDEA does not have the notion of a “workspace,” I like to make a “workspace” directory in order to clearly indicate the location of my code and to keep it separate from other files, such as programming notes.
In a directory of your choosing, make the directories
C: .../cs4760progassign-Workspace/cs4760progassign/
Note that the directory “cs4760progassign” is a sub-directory of the “cs4760progassgin-Workspace” directory. From experience, I have learned that the IntelliJ IDEA will automatically name the project and package of the app by the name of the directory that the project is created in. So it is easier to properly name the project directory within the workspace directory first.
2. Use the wizard to make the Grails project
Run IntelliJ IDEA and point it to the “cs4760progassign-Workspace/cs4760progassign/” directory. If IntelliJ IDEA opens in the wrong directory do not worry. Within IntelliJ IDEA select “File” from the top menu. Then select “New” and then “Project …”. The wizard should open. In the left hand panel of the wizard, select “Application Forge”, NOT “Grails”.
i. Specify the package download in the Wizard
In the right panel, enter the “Project JDK” by clicking the “Add JDK…”. A file browser opens. Navigate to the location of you JDK and click the “OK” button at the bottom.
For “Project Type”, select “Application”.
For “Grails Version”, select “3.3.11”.
For “Profile”, select “web”.
Use the default “Features”
- asset-pipeline
- geb
- gsp
- hibernate5
Then click the “Next” button at the bottom right.
ii. Specify the Project Location
In the next window, you specify the “Project location” by clicking the “…” button. Yet another file browser opens. Navigate to your workspace and project directory, “cs4760progassign-Workspace/cs4760progassign/”. Click the “OK” button at the bottom of the file browser. Note that the “Project name” is automatically filled in the text field.
iii. Create the Project and the Basic App
Click the “Finish” button at the bottom in the Wizard. A notice appears asking if you want to open the new project in “This Window” or in a “New Window.” Click the blue “This Window” button.
IntelliJ IDEA will move to the new project directory, and IntelliJ IDEA begins building the basic Grail app directory structure and files within the project directory. This will take some time, 5 minutes or more. Be sure that you let IntelliJ build the complete directory and files. You can watch the build in the “Build” console at the bottom of IntelliJ IDEA.
Setting up and building the project files the first time takes a long time, several minutes. Let Gradle do it works.
iv. Setup Local Git Repository
You should at least setup a local repository for your project. IntelliJ IDEA setups the project with an gitignore file. IntelliJ comes with version control system functionality. You can find them under “VCS” in the menu bar. I prefer to use the Git Bash terminal. Open Git Bash terminal at the root of the project file, cs4760prograssign/, and enter
$ git init $ git add -A $ git commit -m "initial commit" $ git checkout -b progassign-1
IntelliJ IDEA VCS commands integrate with the git commands enter through Git Bash terminal.
v. Study the Project Directories
After Grails has finished creating the new app, the “Project” view shows the project directories files:
- .gradle/
- .idea/
- build/
- gradle/
- grails-app/ <- This is the main directory that you will be coding in.
- assets/ <- asset-pipeline plugin uses this directory. You will add css and JS files here.
- images/
- javascript
- stylesheet/
- conf/
- spring/
- application.yml <- Configuration file for the running app
- logback.groovy
- controllers/ <- Controllers talk to domains and views
- cs4760progassign/
- UrlMappings.groovy <- configure the url mapping for the app
- cs4760progassign/
- domain/ <- domain models for interfacing with the data store
- i18n/
- Resource Bundle ‘messages”
- messages.property <- English language files.
- other language files
- Resource Bundle ‘messages”
- init/
- cs4760progassign/
- Application.groovy <- file that initialize the program
- Bootstrap.groovy <- groovy code for initializing the app
- cs4760progassign/
- services/ <- for groovy and Java code that run outside the controllers
- taglib/ <- for app specific “g-tags”
- views/ <- for gsp files, like html files.
- Layouts/ <- gsp files for structuring views
- main.gsp
- error.gsp
- index.gsp <- default home view
- notFound.gsp
- Layouts/ <- gsp files for structuring views
- assets/ <- asset-pipeline plugin uses this directory. You will add css and JS files here.
- src/
- integration-test/
- main/
- test/ <- Will appear when unit tests are made
- .gitignore
- build.gradle <- configuration file for building the app
- cs4760progassign.iml
- gradle.properties
- gradlew <- Command file for running grails tasks
- gradlew.bat <- Command file for running grails tasks
- grails-wrapper.jar
- grailsw
- grailsw.bat
- settings.gradle
I have marked a few important directories and files. For the most part, you will do most of your coding in the grails-app/ directory. You will make domain models in the domain/ directory to interface with the data store (database). You will generate controller files to act as the intermediary between the domain classes and the views. Also you will make views, gsp (like html) files in the views/ directory. You will want to know the directory structure below well:
- grails-app/
- assets/
- conf/
- application.yml
- controllers/
- UrlMappings.groovy
- domain/
- i18n/
- intit/
- Bootstrap.groovy
- views/
- layout/
- main.gsp
- index.gsp
- layout/
There are a few configuration files you will use:
- application.yml in grails-app/conf/ – for configuring the running app including the data source
- Bootstrap.groovy in grails-app/init/ – for setting initial values for the running app
- UrlMapping.groovy in grails-app/controller – for configuring the URL
- build.gradle in the project root directory – for specifying plugins and building the app
vi. Run the app
The code generated by IntelliJ IDEA will run. To run the basic app, you only need to click the right pointing green arrow at the top of IntelliJ IDEA. Make sure that the adjacent text field says “Grails: cs4760progassign”. This will open a terminal view at the bottom of IntelliJ IDEA. You can watch the build. When the build is done you should see this message:
|Running application... Grails application running at http://localhost:8080 in environment: development
Also your default browser window should open to http://localhost:8080. If it does not, just open a browser window and paste the URL into address bar. You should see the default index page for a Grails basic app. Congratulations you made your first basic app.
A lot of useful information will appear on this page, including
- Application Status <- Specifics Grails version
- Artefacts <- number of controllers, domains and services.
- Installed Plugins <- list of pulgins and there versions.
- Available Controllers <- List of controller links.
Currently, there are no controllers. The app is showing the index.gsp. You can see the code by clicking on grails-app/views/index.gsp in the Project panel of IntelliJ IDEA. The code will appear in the edit window. For the most part the code is html, but it is accessing some app variables. They are written using ${…} syntax. More about that in other assignments and lectures.
vii. Modify the Basic App: set the context path
Although the index view looks good, I’m not happy with the URL of the index or home view. The URL should be:
http://localhost:8080/cs4760progassign/
and NOT:
http://localhost:8080
We need to edit grails-app/conf/application.yml by adding to the bottom of the file
server: contextPath: '/cs4760progassign'
Note that there is a space between the colon and the quote. Without the space the YML parser will silently fail. An hint that the formatting is correct is that the ‘/cs4760progassign’ should be green in IntelliJ IDEA. Also “server:” and “contextPath: ‘/cs4760progassin'” should be on separate lines. Finally, there must be a leading slash, “/”, or you’ll get an error.
The file application.yml is written in YML which is a markup language:
It is not a particularly complex markup language and more readable than XML. Gradle uses YML. You’ll not need to learn too much of the language. The above line just sets the server context path to the app name. For reference see:
http://stackoverflow.com/questions/32976039/how-do-you-change-the-application-name-in-grails-3
Now stop and rerun Grails. Note IntelliJ IDEA automatically saves edits to a file, so you do not have to save the file. Stop the app by clicking the red square either in the Run view or the tool bar at the top of IntelliJ IDEA. You can also restart the app clicking the small black square with a green arrow.
Step 4: Move the Controller List view
1. Generate the New Controller
The default home page, the controller list view, is useful but not appropriate for a home page, so we should move it. We would like the URL for the control list view to be:
http://localhost:8080/cs4760progassign/controllerList
Create a controller by right clicking the “controllers” directory in “Project” left hand panel, and select “New” and then “Grails Controller”. In the pop-up menu enter
ControllerList
The command creates several files. One of the files is ControllerListController.groovy in grails-app/controllers/ directory:
package cs4760progassign class ControllerListController { def index() { } }
The generated controller has only one method, “index” and the method is blank. This is all we need.
2. Move index.gsp
Also notice that Grails also made a new directory, /views/controllerList/, in the views directory. All the views for the ControllerListController should go into this directory. The names of the views should correspond to the method names in the controller. In this case, we want a view file called, “index.gsp.” To make this view all we have to do is move “views/index.gsp” to “views/controllerList/index.gsp. You can do this by dragginng “views/index.gsp” to the “views/controllerList/” directory. After dragging, IntelliJ IDEA asks if you want to perform any checks and change references. In this case, you don’t. Click “OK.”
Rerun the app.
You should get a 404 or 500 navigating the browser to
http://localhost:8080/cs4760progassign
We should not be surprised, currently there is no root “index.gsp”. If you do not get a 404 or 500 response then Intellj IDEA has refactored the URLMappings.groovy file, and added the line
"/"(view:"/controllerList/index")
Correct the mapping by editing it to be
"/"(view:"/index")
Now you should get a 500.
In the browsers address bar, add “/controllerList” and you will get to the controller list view. Now the list of available controllers has one item in it:
cs4760progassign.ControllerListController
You can click on the link to go to the controller’s index view or action. In this case, clicking on the link will take you to this same view. You can notice that the URL has changed to
localhost:8080/cs4760progassign/controllerList/index
The part of the URL after the controller name is the controller action, in this case the “index” action. With an empty action all that the Grails app will do is display the index view, “index.gsp”.
3. Make Temporary Root Index View
We would like to eliminate the 500 error when the browser is pointed to the root of the app. We only need to make a new “index.gsp” file in the views root directory. Right click on the “views/” directory in the project panel, select “New” and then “GSP”. Now when the window pops up asking for the name of the file, enter “index”. After clicking the “OK” button, the editor window should show a template gsp file for “views\index.gsp.” Cut and paste the html code below into the editor window
<!DOCTYPE html> <html> <head></head> <body> <h1>Base index.gsp</h1> <p>Under construction</p> <p><g:link controller="controllerList">Go to ControllerList</g:link> </p> </body> </html>
The html code should look very familiar to you, if not study, html at w3schools
http://www.w3schools.com/html/default.asp
There is only one tag that should be unfamiliar to you, the g-tag, or more specifically the g:link tag. The g:link tag is a Grails tag that is similar to the html anchor tag. It will make portable links using controller names and actions. Whenever possible you should use g:link tags in your code rather then html anchor tags. This will make it safer to deploy your app. You can read more about the g:link tag at
http://docs.grails.org/latest/ref/Tags/link.html
Rerun the Grails app. Now there is no longer a 500 error, and you can click the link that will take you to the controller list view.
4. Fix Unit Test
You should also notice notice that a new directory, test/, is made in the src/ directory. Navigate into src/test/groovy/cs4760progassign/ directory, and you should see the auto generated groovy file, ControllerListControllerSpec.groovy. This is a Specification file for specifying unit tests for the ControllerListController. Grails uses a Groovy testing platform, Spock:
The documentation is at
http://spockframework.org/spock/docs/1.1/index.html
You should read the primer
http://spockframework.org/spock/docs/1.1/spock_primer.html
https://docs.grails.org/latest/guide/testing.html
We can run all the tests for the app by running the grails command:
> grails test-app
To make the command in IntelliJ, click the field to the left of the green right run arrow or black box with green rerun arrrow at the top. Select “Edit Configuration …”. In the window that pops up, click the plus at the top and select “Grails”. In the right panel, name the test “test-app” and in the “Command line” field enter “test-app”. Be sure to select the “Application”, “cs4760progassign”, in the top field just below the Grails tab. Click “OK”.
To run the tests, select “test-app” in the “Run/Debug” field then click the green arrow. A “test-app” console will open at the bottom which will show the program searching for tests. Eventually, you will see in the console displaying this message:
... :test cs4760progassign.ControllerListControllerSpec > test something FAILED org.spockframework.runtime.SpockComparisonFailure at ControllerListControllerSpec.groovy:16 1 test completed, 1 failed :test FAILED FAILURE: Build failed with an exception. BUILD FAILED What went wrong: Total time: 8.021 secs Execution failed for task ':test'. There were failing tests. See the report at: file:///E:/Data-2012/Classes/cs4760/s21/ProgrammingAssignment/cs4760progassign-Workspace/cs4760progassign/build/reports/tests/test/index.html ...
So a test failed. This is not the easiest test report to read. There several ways to get to the more attractive test report allured to in the message “1 test completed, 1 failed …”. One way is to navigate to build/reports/tests/test/index.html and right click and select “run index.html”. The test report will appear in your web browser. At the same time “index.html” is added to the “Run/Debug” commands, so the next time you run the tests you can access the report by clicking the “Run/Debug” field and select “index.html” and finally click the green arrow. You may want to rename this command by selecting “Edit Configuration…”. You will find the “index.html” command under “JavaScript Debug” category on the left. Change the “Name:” field to “test-app results”.
Inspect ControllerListControllerSpec.groovy
package cs4760progassign import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class ControllerListControllerSpec extends Specification implements ControllerUnitTest<ControllerListController> { def setup() { } def cleanup() { } void "test something"() { expect:"fix me" true == false } }
The test, called a feature in Spock, is a method named by the string “test something”:
"true == false"
This test Is certain to fail. Best practice in unit testing is not to test auto generate code because with 99% confidence we believe it to be correct, so we could remove the test or the feature. But a worthwhile feature/test might be to test that ControlerListController.index() returns status 200 OK. Then running the test assures us that action is still rendering. So replace the “test something” test with
void "test 200 repsonse"() { when: controller.index() then: status == 200 }
Rerun the “test-app”. Now the console returns
... BUILD SUCCESSFUL Total time: 8.746 secs |Tests PASSED Process finished with exit code 0
Test report is still generated and you can view it by selecting the “index.html” in the “Run/Debug” and clicking the right green arrow. You can also see the results by selecting the “test-app results” in the “run/debug” field and click the right arrow.
Step 5: Make Domain Classes
You will implement a simple database scheme. You need two tables in the database, an Author table and a Book table:
Database Scheme:
Author:
- String name
- has many books (list of entries in the Book table)
Book:
- String title
- Integer publishYear
- belongs to author (entry in the Author table)
Read the chapter about GROM in the Grails and GORM documentation to learn how to specify the Domain classes, Author and Book.
http://docs.grails.org/latest/guide/GORM.html
http://gorm.grails.org/latest/hibernate/manual/index.html
Specifically, you’ll want to read carefully the section about GROM association:
http://gorm.grails.org/latest/hibernate/manual/index.html#gormAssociation
You do not need to understand the entire GORM Manual to complete this assignment. The assignment can be completed without deeply understanding GORM.
You should notice that the Author class references the Book class and the Book class references the Author class. This is a cyclic reference and can confuse the auto compiler in the IDE, if you just write the code for the classes sequentially. If you type up the Author class before there is a Book class then your code will have an error at the line referencing the Book class. Instead of writing the classes sequentially, I suggest first making both empty Author and Book domain classes by right clicking on the “domain” folder, select “New” and then “Grails Domain Class”. Fill in the name of the domain class: Author or Book, and finally “OK.” After creating both the Author and Book domain classes, you can type the references to other class without error.
Step 6: Inspect the Database
You will want to inspect the database to verify that it has really made the tables. When you code more complex projects, you can inspect the database directly to see if the SQL actions are preformed as you expect.
Rerun the Grails app and point your browser to
http://localhost:8080/cs4760progassign/dbconsole
This will bring up the database login. Note that in the login screen the JDBC URL should match what is specified in the “grails-app/conf/application.yml” configuration file for the development data soruce entry. So if you running in a “development” environment the JDBC URL should be
jdbc:h2:mem:devDb;MVCC=TRUE
Note the login in screen has a link for “JDBC URL”, click on it and more explanation for the entry.
The user name should be “sa” and password should be blank. Click “Connect” button and data source browser will appear. You should see the Author and Book tables listed in the left panel. Expanding them you’ll see the fields for each table. Clicking the Author table will enter the SQL command:
SELECT * FROM AUTHOR
You can click the “Run” button to execute the command. Do so, and you’ll see that the table is empty because you have not add entries to the table.
Step 7: Add Entries to the Tables.
1. Write Specification Class for AuthorBook
When we made the Author and Book domain classes, Grails auto generated the specification classes AuthorSpec.groovy and BookSpec.groovy in the src/test/groovy/cs4760progassign/ folder. Inspect these specification files. If we run the tests they will fail. The Author and Book domain classes are very simple. If we made persistence test for Author and Book, we would just be testing the GORM code, which is not necessary. If the domain classes had significant constraints specified in the constraint clause, the constraints would be worthy of unit testing. Grails has an excellent guide on unit testing constraints:
http://guides.grails.org/grails-test-domain-class-constraints/guide/index.html
We will save the AuthorSpec.groovy and BookSpec.groovy specification files just in case we add constraints worthy of testing. So comment out the feature tests in both AuthorSpec.groovy and BookSpec.groovy. Because creating Book entries belonging to an Author entry is non trivial, we could test the combined creation of Author and Book. This specification really only test our understanding of the syntax for creating Author and Book entries, but running the unit test is faster then running the entire app. Create a new Specification file in the src/test/groovy/cs4760progassign/ folder called AuthorBookSpec.groovy. Add the following code:
package cs4760progassign import grails.testing.gorm.DataTest import spock.lang.Specification class AuthorBookSpec extends Specification implements DataTest { void setupSpec(){ mockDomains Author, Book } void "test basic Author Book persistence"(){ setup: new Author(name:"Stephen King") .addToBooks(new Book(title:"The Stand", publishYear:1978)) .addToBooks(new Book(title:"The Shining", publishYear:1977)) .save(flush: true, failOnError: true) new Author(name:"Mark Twain") .addToBooks(new Book(title:"Tom Sawyer", publishYear:1876)) .addToBooks(new Book(title:"Huckelberry Finn", publishYear:1884)) .save(flush: true, failOnError: true) expect: Author.count == 2 Book.count == 4 Author.findByName("Stephen King").books.size() == 2 Author.findByName("Mark Twain").books.size() == 2 } }
Note that AuthorBookSpec implements DataTest rather then DomainUnitTest. This is because we need to use mockDomains for Author and Book to create our database entries. In the feature test “test basic Author Book persistence”, we use the “setup” block to create the entries. While creating the entries note that the Book objects must be created before the Author object. The save for the Author object also saves the Book objects to the database. The “expect” block specifies the feature tests. Note that in
Author.findByName("Stephen King").books.size() == 2 Author.findByName("Mark Twain").books.size() == 2
the parentheses after “size” is necessary because otherwise Groovy interrupts “size” as a property of the Book object rather than a method of the list for books.
Run the tests. You can run just this unit test by clicking the double green arrows just to the left of the code for the class. The test should be successful.
2. Bootstrapping the Database
You can use the Bootstrap.groovy file to add entries to the table when the web app is installed on the server. Open “init/cs4760progassign/BootStrap.groovy” in the editor, by double clicking the file in the project panel. Add the code to the init closure, by replacing the file content with below.
package cs4760progassign class BootStrap { def init = { servletContext -> new Author(name:"Stephen King") .addToBooks(new Book(title:"The Stand", publishYear:1978)) .addToBooks(new Book(title:"The Shining", publishYear:1977)) .save() new Author(name:"Mark Twain") .addToBooks(new Book(title:"Tom Sawyer", publishYear:1876)) .addToBooks(new Book(title:"Huckelberry Finn", publishYear:1884)) .save() } def destroy = { } }
Now stop the app and run it again. Use dbconsole to inspect the tables. You should see the entries in both the Author and Book tables.
Step 8: Generate the Controller and Views
Grails will generate the basic controllers and all the views for the domain classes. To make the controllers and views for the Author domain class, right click the project or select “tools” in the menu bar then select “Grails” and then “RunGrails Command”. In the pop up window, type the command below in the command text field.
generate-all cs4760progassign.Author
Make sure the correct project is specified in the “Application” text field. You do not need any “VM options.”
This will make several files for you:
- grails-app/controller/cs4760progassin/AuthorController.groovy
- grails-app/views/author/create.gsp
- grails-app/views/author/edit.gsp
- grails-app/views/author/edit.gsp
- grails-app/views/author/index.gsp
- grails-app/views/author/show.gsp
- src/test/groovy/cs4760progasign/AuthorControllerSpec.groovy
- services/cs4760progassign/AuthorService.groovy
- src/integration-test/groovy/cs4760progassign/AuthorServiceSpec.groovy
You should inspect these files and learn from them. The generated controller uses Services to interact with the Domain object and make the model sent to the view. Because interaction with domain objects can run for extended time, Services are transnational. See the documentation for Grails Services:
https://docs.grails.org/latest/guide/services.html
The file AuthorService.groovy is an interface for the service used by the AuthorController.
The file AuthorServiceSpec.groovy is an integration test specification for the implementation of AuthorService.
Generate the Book controller and all the views. Inspect the generated files.
Step 9: Fix Integration Tests
The files in src/integration-test/groovy/cs4760progassign/ directory, AuthorServiceSpec.groovy and BookServiceSpec.groovy, are integration tests. Integration tests are used to test the communication between classes or components of the application. Because the roles of services are typically to interface between controllers and domain objects it is appropriate to use integration tests. Integration tests run the application before any of the tests are ran. All tests use the same application context. See the Grails documentation about integration testing:
https://docs.grails.org/latest/guide/testing.html#integrationTesting
To run an integration test, we need to create a new run command. Click the drop down button just to the left of the green arrow in the menu bar, and select “Edit Configurations …”. Add a new Grails command by clicking the +. Fill out the panel on the right. Name the command “integration test”. In the “Command line:” text field enter
test-app -integration
Be sure to select “cs4760progassign” in the “Application:” drop-down. Click “OK” button at the lower right.
Run “integration test” now. It fails because of the integration tests specified in AuthorServiceSpec.groovy and BookServiceSpec.groovy. You can view the integration report by opening files:
- build/test-results/integrationTest/TEST-cs4760prograssign.AuthorServiceSpec.xml
- build/test-results/integrationTest/TEST-cs4760prograssign.BookServiceSpec.xml
The reports are in xml format, so they are not very readable. Nevertheless, you can determine the failing tests.
The first cause of the failure in the BookServiceSpec is in the setupData()
private Long setupData() { // TODO: Populate valid domain instances and return a valid ID //new Book(...).save(flush: true, failOnError: true) //new Book(...).save(flush: true, failOnError: true) //Book book = new Book(...).save(flush: true, failOnError: true) //new Book(...).save(flush: true, failOnError: true) //new Book(...).save(flush: true, failOnError: true) assert false, "TODO: Provide a setupData() implementation for this generated test suite" //book.id }
Because the entire application will be running at the time of tests, we do not use MockDomains to make the Book entries. Instead we create the Books as we would normally in the program. The @Rollback annotation will treat each test method as a transaction that should be rolled back at the end of the test. Below is what we should write for the setupData method:
private Long setupData() { // Need to remove all data from tables just in case hibernate is already running. // https://stackoverflow.com/questions/8160296/grails-delete-all-data-from-table-domain-class-i-e-deleteall // Note even the deletes will be rolled backed. Book.executeUpdate('delete from Book') Author.executeUpdate('delete from Author') // Add to the tables Author testAuthor = new Author(name: "Test Author").save(flush: true, failOnError: true) Book book0 = new Book(title: "Test Book 0", publishYear: 1999, author: testAuthor).save(flush: true, failOnError: true) new Book(title: "Test Book 1", publishYear: 1999, author: testAuthor).save(flush: true, failOnError: true) new Book(title: "Test Book 2", publishYear: 1999, author: testAuthor).save(flush: true, failOnError: true) new Book(title: "Test Book 3", publishYear: 1999, author: testAuthor).save(flush: true, failOnError: true) new Book(title: "Test Book 4", publishYear: 1999, author: testAuthor).save(flush: true, failOnError: true) book0.id }
The integration test will use the same application context that run-app uses. So the app should not be running at the time of the test. Even if the app is shut down by the “stop” command, hibernate is still running in the background. Consequently, we need to clear all the tables. The fastest way to do this is to use HQL directly. See
The Rollback will even rollback the deletes from the table, so in fact, the app can be running doing integration tests. As long as the app is inactive, its state will be restored.
Because the Book domain is defined as “belongsTo” to an Author domain object, we need to create an Author first and include it in the constructor call for the new Book. Note that the save methods includes flush and failOnError true. We need to flush to assure that the Book object is made immediately, and we want failOnError because this is a test and should fail on the save rather than later.
The “test get” feature test was written assuming that no other operations were performed on the database. But if you have added to the database, for example using Bootstrap and running the program, then the first id will not be 1. So we patch the “test get” feature test
void "test get"() { Long bookId = setupData() expect: bookService.get(bookId) != null }
We also need to patch the “test list” feature test:
void "test list"() { setupData() when: List<Book> bookList = bookService.list(max: 2, offset: 2) then: bookList.size() == 2 // assert false, "TODO: Verify the correct instances are returned" bookList[0].title.contains("Test Book ") bookList[1].title.contains("Test Book ") }
Also, we need to patch the “test save” feature test:
void "test save"() { when: // assert false, "TODO: Provide a valid instance to save" Author author = new Author(name: "Author").save(flush: true, failOnError: true) Book book = new Book(title: "Test Book", publishYear: 1990, author: author) bookService.save(book) then: book.id != null }
Run “integration test” command. The BookServiceSpec does not fail any test.
You are to patch AuthorServiceSpec.groovy on your own. HINT: an Author object does not need to have any Books to be created.
Step 10: Use the Web App
You can run the web app. Stop and start the app again. The controller list page should show the Author and Book controllers. Click on these controller links. Inspect the index views for the Author and Book controllers. You should navigate and play with the interface.
Use the app to create a new author and add a book to the author. View the Author and Book list to see that it is in database. Also go to dbconsole to inspect the tables directly.
Delete the author “Stephen King” and then view the book list. The books “The Stand” and “The Shining” should be gone from the list because of the static property “belognsTo” in the Book domain class.
Step 11: Change Cross Referencing List Displays
Using the web app, you should have noticed that when you view the list of authors that the list of books are referenced by the table (cs4760progassign.Book) and the id. Also showing a book references the authors by the table and the id. This is not an error, but it might not be what you want. You probably expect that the book reference ought to be the book title and the author reference should be the author’s name. We can change to this behavior by adding a toString method to the domain model.
In the Author domain class, add the lines below:
String toString(){ "${name}" }
In the Book domain class, add the lines
String toString(){ "${title}" }
You’ll need to stop and run the web app for the changes to the domain to take place. Now try the web app, you notice that Author is referenced by name and Book by the title. The code in the toString method tells the view how to render the view. The dollar sign, $, is a grails tag that alert the view that within the curly bracket is groovy code, in this case a property of the class.
In general, you should be careful making these changes. The problem with our change is that the name of the author may not be unique, meaning two authors may have the same name. So, this will cause ambiguity when associating a book with an author. In the next assignment, you’ll see that my general strategy is to use the generated views only for the administrated backend of the website and that we’ll make public views by hand coding. The admin should understand the references to book and author are table and id, and consequently the admin would not be confused.
Step 12: Make Screen Shots and Submit on Canvas
After creating a new author, adding a book and deleting the author “Stephen King”, make a screen shot of the book list. The URL is
http://localhost:8080/cs4760progassign/book/index
Also make a screen shot of AuthorServiceSpec.groovy that you have patched.
Submit the screen shots in canvas for the “Programming Assignment 1 – Building Your First Grails App”.