Darren's Blog

This is my personal Weblog

Integration tests in Grails

clock September 24, 2008 16:04 by author darren

I have written this mostly to help me understand what was going on with my tests. When I first discovered the problem I could not get my head around it but I now think I understand and hope that if other people can understand it they can avoid repeating the problem

I use Linux on my development machine in a team where all other team members seem to be big fans of Windows. From time to time this causes me problems as code written on Windows fails on my machine. Usually this is because people have hard coded file paths for Windows in a test case. This is easy to fix but sometimes other things cause problems. One problem recently took me ages to track down and once I had found it I was not sure what the root cause was. Was it simply differences in Operating Systems or something more sinister - test cases that are not as atomic and robust as they could be?

The problem focuses around two integration tests and I have recreated two tests to demonstrate this. The tests may be a bit contrived but the problem I encountered in the project's tests were caused by code written in exactly the same way. In particular these examples could very easily be moved to a unit test but the reason the original code was an Integration test was that the AuditTrail objects were persisted to a database. I have removed the persistance as its not required to demonstrate this problem and it makes them easier to follow.

The two tests are AuditServiceIntegrationTests and PageControllerIntegrationTests.

AuditServiceIntegrationTest.groovy

Here is the code for the first test and the first object under test. The AuditTrail object is trivial - it has just two attributes: description and eventDate so the code is not shown here.

 

 1 package net.darren
 2 class AuditServiceIntegrationTests extends GroovyTestCase {
 3 
 4     def auditService;
 5 
 6     void testAuditTrailEventTimeChanges() {
 7 
 8         AuditTrail trail = auditService.createAuditTrail("Feed Updated: a_feed")
 9         Thread.sleep(500)
10         AuditTrail trail1 =  auditService.createAuditTrail("Feed a_feed Failed: Null Pointer Exception")
11 
12         assertFalse "Time should be different", trail.eventDate.equals(trail1.eventDate);
13     }
14 }

 

 

 1 package net.darren
 2 class AuditService {
 3 
 4     static transactional = false
 5 
 6     public AuditTrail createAuditTrail(String auditDescription) {
 7         AuditTrail trail = new AuditTrail(eventDate: getDate(), description: auditDescription)
 8 	return trail
 9     }
10 
11 
12     def getDate = {
13         new Date()
14     }
15 }

 

This is a simple test that calls the createAuditTrail() method which returns a new AuditTrail object. It then waits for a small amount of time before calling it again to create a second instance of the AuditTrail class. The test is that each object has a different eventTime because they were created at different times. The code under test is also simple. The interesting part to note is the getdate() closure in AuditService this was probably done deliberately so that it could be stubbed or mocked during testing. This test on its own passes fine, the two dates are different because of the delay between each call to auditService.createAuditTrail()

Enter another test: PageControllerIntegrationTests

Now I introduce another test. This one requires the getDate() method to be stubbed out which is done in the last line of the setUp method. The stub version changes the functionality so that the time returned is always the same (Different to the real functionality which always returns the currently time which, in my universe at least, is constantly changing). There are no actual tests for this object as it does not matter for the purposes of problem - the damage is already done in the setUp() method.

 

 1 package net.darren
 2 class PageControllerIntegrationTests extends GroovyTestCase {
 3 
 4     PageController controller
 5     def auditService;
 6     Calendar constant = Calendar.getInstance()
 7 
 8     void setUp() {
 9         controller = new PageController(auditService:auditService);
10         Calendar now = Calendar.getInstance()
11         now.setTime(constant.getTime())
12         now.add(Calendar.YEAR, 1)
13         auditService.getDate = {
14 		println "stub created in PageControllerIntegrationTests has been called";
15 		return now.getTime()
16 	}
17     }
18 
19     void testSomething() {
20         /* no need to do anything damage is already done */
21     }
22 }

 

You may run this code and all tests will pass, for there is one last twist to this problem. The order the tests run matters. How you get them to run in a different order is dependent upon platform, On my system (Fedora 8 and Java 1.6.0_06 64 bit) I moved the test classes to different packages but other systems may require different re-factoring to see the behavior I describe. Windows XP seems to have a much more logical loading order - it does it alphabetically so AuditServiceIntegrationTests runs followed by the PageControllerIntegrationTests. So just rename PageControllerIntegrationTests to AardvarkPageControllerIntegrationTests to make it run before AuditServiceIntegrationTests

If on my system I keep both tests in the same package then AuditServiceIntegrationTests runs followed by the PageControllerIntegrationTests and everything is fine: both pass.

-------------------------------------------------------
Running 2 Integration Tests...
Running test net.darren.AuditServiceIntegrationTests...
testAuditTrailEventTimeChanges...SUCCESS
Running test net.darren.PageControllerIntegrationTests...
testSomething...SUCCESS
Integration Tests Completed in 1180ms
-------------------------------------------------------

If, however, I move PageControllerIntegrationTests from package net.darren.PageControllerIntegrationTests to net.PageControllerIntegrationTests they run PageControllerIntegrationTests followed by AuditServiceIntegrationTests and AuditServiceIntegrationTests will fail.

-------------------------------------------------------
Running 2 Integration Tests...
Running test net.PageControllerIntegrationTests...
testSomething...SUCCESS
Running test net.darren.AuditServiceIntegrationTests...
testAuditTrailEventTimeChanges...FAILURE
Integration Tests Completed in 1100ms
-------------------------------------------------------

The reason it fails in this case is that the call to getDate() in AuditService during the execution of AuditServiceIntegrationTests actually calls the stub version that was set in PageControllerIntegrationTests and since this always returns the same time both objects are created with the same time and the assertion fails.

Tests should run without interferring with each other and the order they run should not be an issue. So why is it a problem in this case? Note that nowhere in my code is the object auditService instantiated and there is not even a whiff of a NullPointerException. This is because the tests rely on Spring and its autowiring functionality (autowiring means to look for objects defined in Spring with the same name as your object property and automatically assign the object to the property) and as all objects in Spring are singletons by default, once you stub out a closure it stays stubbed out. The quick answer is to create a new version of the object to be stubbed in the setup() method that overwrites the one assigned by spring and stub that one. That way all other tests will continue to use the unmolested spring created version.

Really I was left not knowing what to blame, is it just unfortunate that my system loads and runs the tests in a different order to everyone else? Is it bad practice to rely on the running order of tests for them to work? Or should we be avioding stubs in Integration tests? Is it good practice to override the spring autowiring stuff by creating your own version of a property in the setUp method? Should we be refactoring some of our Integration tests into unit tests? Does everyone in the team understand spring autowiring and is aware that Integration tests use it? (I was not aware it was being used when I first started writing integration tests in Grails) Have I asked enough questions?

The source code as an IntelliJ project is available here.
TestingWithStubs.zip (357.31 kb)

syntax highlighted by Code2HTML, v. 0.9.1

Currently rated 4.0 by 1 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Back to pure Java

clock September 18, 2008 21:38 by author Admin
I should be moving to a new job soon which will mean no more Groovy and Grails. Its been an interesting few months but I'll be glad to be writing Java again. Goovy is OK but I don't have the head for dynamic languages and never seem to make the most of there capabilities. Perhaps more experience would help and the new stuff my current team are about to start working on may have given me the experience I need. Unfortunately I will not get to find out because I want the challenge of a new position and extra responsibility, more money will be nice too...

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


People reading my posts

clock September 18, 2008 21:35 by author Admin
Thanks to the people who have been reading my posts, I'll try to make them more interesting in future.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


2008 F1 World Championship

clock September 18, 2008 21:31 by author Admin

So Kimi Raikkonen says he needs a miracle to retain the F1 crown this year (Article here). Well, don’t worry too much Kimi, I am sure that behind the scenes the FIA is working flat out to invent one for you. Sealed

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Top Tip

clock September 18, 2008 21:28 by author Admin
I have a top tip for people setting up BlogEngine.NET, when you set it up make a note of the admin password you set up. I did not and just spent an hour hacking data files to reset it. I could not find a 'Password Reminder' anywhere in the application. I set this up in July and have not posted since. I should start posting stuff....

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


The bike is gone.

clock July 19, 2008 19:57 by author darren
My motorbike sold a couple of weeks ago and the new owner came to pick it up last week. Was a bit disappointed that he turned up with a trailer instead of riding it but I suppose thats up to him. My next task is to put together some money and get a new one. The biggest question (apart from: where the hell will I get the money) is what to buy. I don't want one of the new breed of exteme sports bikes but I do want something fast and capable of touring. Perhaps a CBR1100 but these are big and heavy or a VFR800 - too much of a touring bike for me. Will be spending the next couple of months looking around and hopefully get something so that I am all ready for next summer.

Currently rated 4.0 by 1 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


More problems...

clock July 19, 2008 19:48 by author darren
OK, so I have got the application working but I tried to follow the steps in the install video available on the blogengine website but I am unable to login with the new user I create.I can still login with the admin user and thankfully I can change the password for that account.I can also post using the new user as the author (this post is authored by 'darren' and not 'Admin') but I am still logged in as 'Admin'.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


My first entry

clock July 19, 2008 19:13 by author Admin
Well here it is, my first blog. Setting up was actually not that bad. The application is simple to install and the BlogEngine website includes a short video which tells you clearly how to install and set it up. Most of the problems come from configuring the web servers. This would be easy if you know what you are doing but I do not. Managed to learn a bit about the admin of my site through the new control panel but still have a long way to go. The domain name is still not set up correctly, I can access it from darrenclarke.net but not from www.darrenclarke.net. I have no idea what the difference is since I am a complete duffer when it comes to these things but once my domain name is transferred to the same provider that hosts the site I should be able to get it working correctly. I can then work on setting up the blog.darrenclarke.net sub-domain. Until then this will do.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


About Me

My name is Darren Clarke. I am a Java Developer working in West London. I decided that the easy option of signing up to Google's blogger.com would be far too easy so spent a frustrating Saturday installing this blog on my personal website. Don't know yet if I will blog very often but since no one is likely to read it - does it matter?

Recent posts

Recent comments

Comment RSS

Page List

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in  anyway.

    © Copyright 2008

    Month List

    Calendar

    <<  January 2009  >>
    MoTuWeThFrSaSu
    2930311234
    567891011
    12131415161718
    19202122232425
    2627282930311
    2345678

    View posts in large calendar

    Sign in