| Unit testing: testing service in 3 different ways (part I) |
|
Let's start with simple unit testing. If someone will be interested I could improve it with more complicated features. In part I we will use ExpandoMetaClass approach for unit testing.
For instance, we have simple Document domain class:
class Document { String name String description Date creationTime Date lastModifiedTime Long revisionCount User owner
static constraints = { name(blank: false, maxSize: 64) description(nullable: true, maxSize: 1024) }
static belongsTo = [User]
String toString() { """Document [name: ${name}, description: ${description}, lastModifiedTime: ${lastModifiedTime}]""" }
} And let's create a simple Document service for handing save and retrieve operations:
class DocumentService {
def saveDocument(Document document) throws InvalidDocumentException { if (document.validate()) { document.save() } else { throw new InvalidDocumentException( document: document) } }
def retrieveDocumentByIdAndOwner(Long id, User owner) throws DocumentNotFoundException { Document document = Document.findByIdAndOwner(id, owner) if (!document) { throw new DocumentNotFoundException([id: id, owner: owner].toMapString()) }
document }
} Lets create simple unit test for listed service using ExpandoMetaClass approach. As far as You remember during unit tests Grails does not inject any of the dynamic methods present during integration tests and at runtime. So we have to mock them in our tests. class DocumentServiceTests extends GroovyTestCase {
def documentService
void setUp() {
// We have to initialize service class, // there is no dependency injection. documentService = new DocumentService() }
void testSaveDocumentSuccess() { def documentToSave = new Document( name: "Document name", description: "Document description", lastModifiedTime: new Date(), creationTime: new Date(), revisionCount: 1L, owner: new User())
// Dynamically add (mock) method validate to all // instances of Document class and // suppose it always return true. Document.metaClass.static.validate = { return true }
// Dynamically add (mock) method save to all // instances of Document class and // suppose it always return true. Document.metaClass.static.save = { return true }
// Saving will pass cause validate and // save methods will return true. documentService.saveDocument(documentToSave) }
void testSaveDocumentFailure() { def documentToSave = new Document( name: "Document name", description: "Document description", lastModifiedTime: new Date(), creationTime: new Date(), revisionCount: 1L)
// Dynamically add (mock) method validate to all // instances of Document class and // suppose it always return false. Document.metaClass.static.validate = { return false }
// Saving will fail cause validation will fail. shouldFail(InvalidDocumentException) { documentService.saveDocument(documentToSave) } }
void testRetrieveDocumentByIdAndOwnerSuccess() {
// Mock dynamic finder method to all // instances of Document class and // suppose it always return some document. Document.metaClass.static.findByIdAndOwner = { Long id, User owner -> return new Document(id: id, name: "Document name", description: "Document description", lastModifiedTime: new Date(), creationTime: new Date(), revisionCount: 1L, owner: owner) }
// Test the target method, // it will return supposed document. def foundDocument = documentService.retrieveDocumentByIdAndOwner( 1L, new User())
assertNotNull foundDocument assert "Document name" == foundDocument.name assert "Document description" == foundDocument.description assert 1 == foundDocument.revisionCount }
void testRetrieveDocumentByIdAndOwnerFailure() {
// Mock dynamic finder method to // all instances of Document class and // suppose it always return NULL. Document.metaClass.static.findByIdAndOwner = { Long id, User owner -> return null }
// Test the target method, it will fail cause // no document instance will be found. shouldFail(DocumentNotFoundException) { documentService.retrieveDocumentByIdAndOwner( 1L, new User()) } }
} As You can see using ExpandoMetaClass is quiet easy. But be aware of using such approach cause some nuances are present:
|