Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save CaptainOfFlyingDutchman/c8c9035e92cd5ec34ca2 to your computer and use it in GitHub Desktop.
Save CaptainOfFlyingDutchman/c8c9035e92cd5ec34ca2 to your computer and use it in GitHub Desktop.
Upgrading a Grails app from version 1.3.7 to version 2.4.4

Upgrading a Grails app from version 1.3.7 to version 2.4.4

So, you want to upgrade your app to Grails 2.4.4 from that legacy 1.3.7 version today!

But wait! There are few challenges waiting for you in the process.

1. Start


Thinking, from where should we start upgrading, as it was quite a task for such an enterprise application upon which whole business is running. Any bug introduced in the process and you are toast! But here comes the tests to rescue us! You may want to ask now what kind of tests? Please wait till I come to tests. I'll explain them, I promise.

Lets start, with some theory and forth coming problems!

  • Upgrading an app is just not about changing the version number in the application.properties file. It's more than that! You've got 'n' number of plugins, some of them you've made in-house which are served from your repository and hosted by your centrally running Artificatory instance! And of course the plugins from Maven have their own story to speak up. There is more to plugins, remember inplace plugins? you have to upgrade them as well.

  • Next is the time, you realize that your code is misbehaving and you don't have any idea about what happened! Means, you just know in which domain the app is operating but you have no clue what are the relationships established between objects by the developers and you need to be sure if these relationships are intact after upgrade (Here you are asked to upgrade someone else's app).

  • You've got different environment settings and config properties in BuildConfig.groovy and Config.groovy files. They would need your special attention now, by saying so I mean the versions i.e. checking for their compatibility.

  • Your app is utilizing Grails' i18n system and users are managed according to their locale. You know there is a lot of work going on here, but rest assure as Grails and java.util.Locale class has your back.

  • You've a very large UrlMappings.groovy file which contains the enormous entries of urls, and if that's not enough you've got them in different localized versions!

These were some of the problems I just mentioned.

2. Preparation and thought process


Every new version of Grails provides some new features improvements and update to old or deprecate features i.e., TagLibs, GSPs, Controllers get some new enhancements with every new release. Among these most notable improvements are Tests.

Since there were quite major changes between Grails version 1.3.7 and 2.4.4 e.g., ApplicationHolder, ConfigurationHolder were deprecated in 2.2.4 but were removed in 2.4.4, etc. So, instead of jumping directly to most recent version at that time (2.4.4), we started a series of upgrades from 1.3.7 to 2.2.4 and then to 2.4.4. this way we insured that our application is not breaking and thus saved ourself from disasters.

The app which we just upgraded had almost 100% test coverage which included unit, integration and functional tests. Here are few things that should be kept in mind while working with prior written tests cases:

  • Well, things keep on changing and so does our testing frameworks, however their working doesn't, thanks to the Abstraction concept, which allows us to continue using something regardless of inner working and utilizing the same interface and syntax, but not the semantics, of course.

  • You should not on any cost alter the assertions of the tests (or specifications as we say them in Spock), whether these are unit, integration or functional. If tests are failing then there is some flaw introduced during upgrade or the previous version code simply doesn't work any more (I'll show an example regarding it later). This point is extremely important to have an upgraded app that works as it should (you mark my words on it)!

  • I heard that we have functional tests also! What are they? You may ask. These are Selenium tests running in disguise of Geb, to automate the browser, so that you don't have to check it manually every time, e.g., Is login still working? Just execute some Geb test and it shall verify for you. One more note here is that these tests only work with specific version of browsers. For our case we had to use Firefox 32.0.1, (you can also use Chrome). Of course, it entirely depends on the version of Selenium library and the driver.

3. Upgrade


So, it's time that we actually upgrade the app by leaving behind our observations but sticking to them all the time.

1. First things first

Here I'll describe the initial steps to be taken in order to upgrade the app. These are essential and no one should lost in explanation.

  • You might want to start with tweaking the values in your Config.groovy and DataSource.groovy file. Well, instead of modifying these files you are encouraged to provide your own external config file like my-config.groovy and adding it to grails.config.locations property in Config.groovy. The configuration in this external file will then override your configuration in Config.groovy and DataSource.groovy file. I'm recommending external config file because everyone can have their own settings in the same file. Of course this file should not be committed to the repository and must be in ignore list of the VCS. This is a nice idea. Is it not?

  • One most notable configuration I found useful was to enable reloading of GSP changes. Grails usually does the trick automatically, but in my case it broke. If it's the case with you also then try adding these properties in your external config file:

    grails.gsp.enable.reload = true
    grails.reload.enabled = true
  • Next we should start upgrading dependencies in our BuildConfig.groovy file. Find the dependencies on Maven repository which are compatible with current Grails release, grails plugins directory would provide a great help on this. Basically, what we are trying to achieve here is that on upgrading the app we would have all our dependencies and plugins resolved, so that any code using those won't break/die at compile time.

2. Fixing tests

As I said earlier, I would talk about tests and explain why are they so much needed. I won't talk about TDD specifically as the code and tests have already been written beforehand.

  • Unit tests

    Are minimal of them, although they can't promise the whole integrity of the app because they are used to test only a unit of work e.g., methods and classes. And I said that because there's a saying -- 100% unit test coverage doesn't guarantee a working app, however your modules would be working individually. Regardless it, unit tests are a necessity.

    In Grails, unit tests are mainly supported by Spock. The working of tests from version 1.3.7 to 2.4.4 has changed much in respect of syntax and as well as in semantics. So to fix the tests we should use new Annotations and behavior provided by the testing framework, and you're good to go. e.g., To test a Service class say MyService.groovy you would go as follows:

    // Class under test
    class MyService {
      def someMethod() { return "returning a string." }
    }
    
    // Test written with version 1.3.7
    import grails.plugin.spock.Unitspec
    
    class MyServiceUnitSpec extends UnitSpec {
      def myService
    
      def "testing someMethod"() {
        when:
          def result = myService.someMethod()
        then:
          "returning a string." == result
      }
    }
    
    // Now same test written with version 2.4.4
    import spock.lang.Specification
    
    @TestFor(MyService)
    class MyServiceUnitSpec extends Specification {
    
      def "testing someMethod"() {
        when:
          def result = service.someMethod() // Notice the service object!
        then:
          "returning a string." == result
      }
    }

    As we can see here we upgraded our test to newer version. For this we had to change the semantics, changing the way how we now approach the respective class under test.

  • Integration tests

    You can follow the same steps mentioned above for unit tests. As we all know, integration test run a particular feature in whole. So, they behave more or less like unit tests.

  • Functional tests

    Functional tests are much different from our unit and integration tests, both in functionality and implementation part. We use a separate library call Geb to implement these tests. Geb basically, handles the pages which are shown on the UI. It helps us to automate the page behavior, e.g., Does that pop-up box open with certain content inside it, if we click that button without filling the required textbox on product search page? The questions like these needs to be manually tested/answered every time whenever we change our code, and it's where functional tests shine. Geb tests require some extra time and needs to be updated whenever our view logic changes, but investment is future proof!

    Keep in mind that to check if functional tests are working properly we need to run the app, and if anything breaks in test, it's either fault in code or in test itself. To determine where does the fault lie, here are some quick tips:

    1. Is there any element missing on the page or its id or class attribute has changed, and you are not able to identify the element? It may be due to change in our css rule definition or refactoring.
    2. Are we testing any div or span for visibility? It's a common source of failing tests as the required value doesn't simply get filled inside the element under test.
    3. Sometimes it's the missing waitFor method that is causing the test to fail.
    4. You must use Browser.via() instead of Browser.to(), iff your page uses redirection.

    These were some guidelines, but none of them were related to the upgrade process of our app. So, lets turn up to them now:

    1. The very first problem we usually face -- What data is being passed from every controller.action to the view. Before we fix this, we should address one more problem. As there could be many controller.action redirects, it is quite hard to find correct controller.action executing right now. Moreover, these controller.action were heavily mapped using UrlMappings.groovy file. Seeing the problem? So one solution I found was to create a Filter that would list every controller, action, params and resulting model on each controller.action call/request.

    Of course, I could have used an already created filter, but that would be overkilling! So, I just created a new Filter as follows:

    // Prints every route and associated data
    class AppDebugFilters {
        def filters = {
          all (controller: '*', action: '*') {
            before = {
              log.debug "..........AppInfoFilters Start........"
              log.debug "$controllerName/$actionName : $params"
            }
            after = { Map model ->
              log.debug "$model"
              log.debug "..........AppInfoFilters End.........."
            }
            afterView = { Exception e ->
              // Don't need it
            }
          }
        }
    }

    Watchout!!! Make sure you remove this filter when you deploy on production, as, it would printout every detail in logs!

    1. Now we are able to detect which url is being hit by the app. But another point is what if some assertion fails in your test? As I said, this is a case where your current code no longer works after the upgrade. Suppose you've got a sitation as follows in GSPs and inside our Geb test we are counting elements based on applied css class:
    // BabyController.groovy
    class BabyController {
        def babbies() {
          def baby = new Baby()
          [newBornBaby: baby] // There could be other parameters also e.g., all the babbies in system currently.
        }
    }
    
    // babbies.gsp
    <g:render template="list" model="[baby:newBornBaby]" />
    
    // _list.gsp
    <g:render template="baby" />
    
    // _baby.gsp
    <g:set var="clazz" value="${baby ? 'cute' : 'very cute'}" />

    Can you guess now what would be the effect of using baby inside _baby.gsp, which was not explicitly passed to it and the value of the clazz variable? Here is the answer: clazz will always contain cute as its value. Why? Because baby model key was defined to be a Baby object, and in this case it's never null. You got the point, right?

    So, what's the solution? It's as simple as that you pass your models explicitly as follows:

    // _list.gsp
    <g:render template="baby" model="[baby: false]" />
    1. This step discusses about configuring the Geb itself so that you would be in position to test the app manually when browser opens, as normally browsers are tend to get shutdown after a failed or successful test. So make sure, that we never add a quit() method call as follows:
    FirefoxDriver driver
    
    def setupSpec() {
      driver = new FirefoxDriver(new FirefoxProfile())
    }
    
    def cleanupSpec() {
      driver.quit() // Never do this!
    }

    Even, if this driver.quit() line is written somewhere, then just comment it out. The importance of removing this statement is, it would preserve the state of the browser session and you would be able to manually inspect the elements on the page. You would also be able to answer the questions like, if there is something wrong going on with the AJAX call (which is made on click of a button), by just checking it inside the browser console.

4. Conclusion/Final Result/Advice


So it was all about testing and upgrading (okay, not everything!). To upgrade an app is really not a simple task, and when you don't have idea about the code but except the logic, situation becomes really complex. In this case you are just relied on tests, whether they are unit, integration or functional.

You would be thinking that I'm little biased towards testing. Actually I'm. To test the app or some feature every time manually is not something that I say is wise decision. Suppose, I changed some code in one part of app. Now how can I be sure if some other module in the app is not affected due to this change or affected altogether? Just automate the things using tests and you are good to go. There were situations when I modified some code to get pass a test but due to the change I made some other tests started to fail. This way I didn't have to test the app again (manually), instead tests provided me feedback. Seeing the power of tests!

People usually say they have 100% unit tests, and should they still need integration and functional tests? And answer is, YES. Unit tests only guarantee that every unit would work fine. As an app works with individual units combined together they should be tested as a group. And this is where integration tests helps us. You might say then why functional tests? Functional tests are necessary to see if software is working as the user is asking it to. Finding the difference between integration and functional? Integration tests are just to ensure all units are working together to produce desired results, and functional tests are just to ensure software is working as per the user requirements and desired behavior.

If someone comes here and ask me, will my app be working fine if I've all the tests (unit, integration and functional) passing? And my experience says, YES!

So, it was about the upgrading of a Grails app. This might not be the complete checklist but you got the idea where to tackle the problem and come up with a solution.

Thanks for joining in the conversation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment