I have a several grails controllers that I generated and modified slightly. I'm working with the generated unit tests and getting them to pass, but I think I'm doing it the hard way. This is what I have.
package edu.liberty.swiper
import grails.test.mixin.*
import org.junit.*
#TestFor(AttendanceController)
#Mock([Attendance, Location, Reason, Person, LocCapMode, GuestContactMode, UserAccount])
class AttendanceControllerTests {
def location
def reason
void setUp() {
def capMode = new LocCapMode(description: "loc cap mode", username: "testuser").save(failOnError: true)
def guestMode = new GuestContactMode(description: "Guest Contact Mode", username: "testuser").save(failOnError: true)
location = new Location(description: "foo", locCapMode: capMode, username: "testuser", guestContactMode: guestMode).save(failOnError: true)
reason = new Reason(description: "test reason", username: "testuser").save(failOnError: true)
def person = new Person(firstName: "John", lastName: "Smith", lid: "L12345678", mi: "Q", pidm: 12345).save(failOnError: true)
def userAccount = new UserAccount(pidm: 12345, username: "testuser").save(failOnError:true)
}
def populateValidParams(params) {
assert params != null
params.personId = '12345'
params.username = "testuser"
params["location.id"] = location.id
params["reason.id"] = reason.id
params.timeIn = new Date()
}
void testIndex() {
...
}
void testList() {
...
}
void testCreate() {
...
}
void testSave() {
controller.save()
assert model.attendanceInstance != null
assert view == '/attendance/create'
response.reset()
populateValidParams(params)
controller.save()
assert response.redirectedUrl == '/attendance/show/1'
assert controller.flash.message != null
assert Attendance.count() == 1
}
void testEdit() {
...
}
...
What I'm hoping for is the ability to dynamically mock domain object, i.e. expect(Attendance.save()).andReturn(null) or expect(Attendance.save()).andReturn(testAttendance), so that I don't have to create the web of associated objects in my setUp method that are necessary to validate the domain object that is being manipulated by the controller.
Am I just looking at this all wrong? It seems like I should be able to decouple the controller logic from the validation logic., so that I can just tell the mock to tell the controller that validation passed or failed. Thanks in advance.
I don't think there is a way to tell the mock that the validation of a certain object that is handled by a controller passed or failed but I might be wrong. But as I understand it your main concern is the creation of the web of associated objects right?
Without knowing what your controller looks like I would guess that you are getting needed domain objects in your controller by ID (e.g. Location) and load a Person by pidm and so on.
To simplify the creation of needed domain objects you could use .save(validate: false).
Your setUp method could look like this:
location = new Location().save(validate: false)
reason = new Reason().save(validate: false)
If you only need objects with valid IDs this would be sufficient.
new Person(pidm: 12345).save(validate: false)
new UserAccount(username: "testuser").save(validate: false)
Set certain fields to be able to use a finder like UserAccount.findByUserName().
So if your controller does something like
location = Location.get(params["location.id"])
reason = Reason.get(params["reason.id"])
userAccount = UserAccount.findByUserName(params.username)
...
new Attendance(location: location, reason: reason, userAccount: userAccount, ...)
the aforementioned lines should be satisfactory for your setUp method.
.save(validate: false) is very useful to just set values that are really needed in your test. I hope I got the whole thing right and I could be of help.
When mocking for unit tests, you don't have to have a complete object graph with every required value to test a single domain. For example, you could have something like this..
def department = new Department(name: "Accounting").save(validate: false)
def user = new User(username: "gdboling", department: department).save()
Assuming the only 2 required fields for User are username and department, but department might have many other fields that would fail validation, this will still work if all you really need to test is User.
You still have to specify them in #Mock, you just don't have to populate every bloody field. :)
Related
I have a set of basic unit tests in grails and I am getting some strange, inconsistent behaviour. So basically the unit tests are for a Service for interacting with my User Domain.
The issue is at a high level - If I run the full Spec of tests - 4 tests fail each time. If I run them individually - they pass. The obvious thing here is that there is state being held between tests - but given this is a unit test - there shouldn't be (and I have verified that with logging).
The components in play here are:
UserService
User Domain
Address Domain
UserCommand (Verifiable)
So looking at the Spec I have the following in setup:
User user
def setup() {
println "================ Setting Up User (${User.count()}) ================"
// Set the roles
new Role(authority: "ROLE_ADMIN").save()
new Role(authority: "ROLE_OPERATOR").save()
def role = new Role(authority: "ROLE_USER").save()
def userAddress = new Address()
userAddress.with {
name = "Name"
address1 = "Address 1"
address2 = "Address 2"
townCity = "Town"
postcode = "BT19"
county = "Down"
}
// Create an admin user
user = new User()
user.with {
christianName = "Phil"
surname = "Preston"
email = "p#p.com"
username = "admin"
password = "password123"
phone = ""
skype = ""
address = userAddress
}
println(user.properties)
// Save the test user
if (user.validate()) {
println "Saving"
user.save(flush: true, failOnErrors: true)
UserRole.create(user, role)
}
else {
user.errors.allErrors.eachWithIndex { i, x ->
println "${i} - ${x}"
}
}
assert user
}
And the two simple tests are as follows:
void "Test updateUser - changing a user password"() {
given: "An update to the user"
UserCommand cmd = new UserCommand()
cmd.with {
id = user.id
christianName = user.christianName
surname = user.surname
username = user.username
email = user.email
skype = user.skype
phone = user.phone
password = "newPassword"
passwordCheck = "newPassword"
isAdmin = user.isAdmin()
isOperator = user.isOperator()
}
when: "attempt to update"
User updated = service.updateUser(cmd)
then: "the password should be update - but nothing else"
updated.password == cmd.password
}
void "Test updateUser - changing a user detail"() {
given: "An update to the user"
UserCommand cmd = new UserCommand()
cmd.with {
id = user.id
christianName = user.christianName
surname = user.surname
username = user.username
email = "update#update.com"
skype = user.skype
phone = user.phone
password = user.password
isAdmin = user.isAdmin()
isOperator = user.isOperator()
}
when: "attempt to update"
User updated = service.updateUser(cmd)
then: "the email should be update - but nothing else"
updated.email == cmd.email
}
(There are others - but this is all to demonstrate the problem)
So the strangeness is as follows:
The first test passes, the second one fails.
If I swap the order - the first one still passes, second fails
If I run both individually - they both will pass
The code in setup() prints the number of User objects (0) each time
It verifies the object is created each time (assert and logging)
The unit test fails because I throw an exception when validation fails forthe User domain object I am udating. So the following:
def updateUser(UserCommand userCommand) {
assert userCommand
// Get the current
User foundUser = User.findById(userCommand.id)
def wasAdmin = foundUser.admin
def wasOperator = foundUser.operator
// Bind Data
bindData(foundUser, userCommand, ['class', 'operator', 'admin', 'address'])
foundUser?.address?.with {
name = userCommand.name
address1 = userCommand.address1
address2 = userCommand.address2
townCity = userCommand.townCity
county = userCommand.county
postcode = userCommand.postcode
}
// THIS VALIDATION FAILS ON SECOND TEST
if (foundUser.validate()) {
foundUser.save()
// ... removed
return foundUser.refresh()
}
else {
Helper.copyErrorToCmd(foundUser, userCommand)
log.error("Errors: ${foundUser.errors.allErrors}")
throw new UserServiceException(cmd: userCommand, message: "There were errors updating user")
}
The errors on the User domain object are basically (shortened):
'skype': rejected value [null]
'phone': rejected value [null]
So you can see that I have empty strings for these fields - and with conversion to null (as Grails will do), this is the reason. However the issue with that is:
It should fail for both, and certainly fail when run individually
Both fields are marked as nullable: true in the constraints
In UserService I have used the debugger to check the objects that are being saved when running both tests - the phone and skype are null for both tests, yet one fails and one doesn't
So the constraints in the User domain object are as follows:
static constraints = {
christianName blank: false
surname blank: false
username blank: false, unique: true, size: 5..20
password blank: false, password: true, minSize: 8
email email: true, blank: false
phone nullable: true
skype nullable: true
address nullable: true
}
And I am using a Command object which has the following constraints:
static constraints = {
importFrom User
importFrom Address
password blank: false, password: true, minSize: 8
passwordCheck(blank: false, password: true, validator: { pwd, uco -> return pwd == uco.password })
}
Note I have even tried adding the Skype and phone fields in the UserCommand constraints closure as well.
This issue goes away if I add text to the fields in setup, but thats not going to possible in the actual app as these fields can be left blank.
Any help on how this inconsistency happens would be greatly appreciated.
RECREATION
I have added a minimal GitHub project which recreates the issue: Github Project Recreating Issue. To see the problem:
./grailsw test-app
Will run the two test, first passes second fails. To run the failed test individually run:
./gradlew test --tests "org.arkdev.bwmc.accountmission.UserServiceSpec.*Test updateUser - changing a user detail"
And you can see the test pass.
The issue seems to be that the skype and phone fields are nullable:true for the first test, and nullable:false on the second test (I step into user.validate(), and step into the GrailsDomainClassValidator.java, in the validate(Object,Errors,boolean) call I can see the constrainedProperties Map (which is basically field -> constraints). This shows that skype is NullableConstraint.nullable == true, on the first call of setup, but is showing that NullableConstraint.nullable == false on the setup call for the second test.). This makes no sense.
After the first call
bindData(foundUser, userCommand, ['class', 'operator', 'admin', 'address'])
in UserService.updateUser(UserCommand) (called by feature method Test updateUser - changing a user password) the static member User.constraints becomes null. Go figure. I have no idea whatsoever how Grails works, so maybe someone else can make sense of it. But to me it seems that this should not happen. Maybe you are somehow making a mistake in that call.
You shouldn't have to test the constraints in the framework technically but I've had similar issues before so I can see why you would want to.
Is it possible to post your full test? Are you using #TestFor/#Mock at all? Just as a check, but I assume you must have #Mock otherwise you would get a different error creating the domain classes? #Mock is required for the constraints to be used (or #GrailsUnitTestMixin afaik).
You say in your comment "The values in your app can be blank"; it should be noted that this is not the same as nullable as well. If it really can be blank you should change/add it and then "" will work
You also are not using 'failOnError: true' as you have typoed it.
Sorry I don't have enough rep to write as a comment.
I am new in unit-testing in Grails application using Spock. However I would like to ask the following question. Lets say I want to run a test for the following function testfun.
class TestFun{
boolean testfun(long userId, long orderId){
User user = User.findByUserId(userId)
if(user == null)return false
Order order = Order.findByUserAndId(user, orderId)
HashMap<String, Object> = orderContent
orderContent= order.orderContent // the order has an element orderContent for storing the elements that one user orders
if(orderContent!=null){
orderContent.put("meal",1)
order.orderContent = orderContent
return true
}
return false
}
}
The corresponding unit test in that case would be:
class TestFun extends Specification {
def setup() {
GroovySpy(User, global: true)
GroovySpy(Order, global: true)
}
def "test funtest"() {
User user = new User(2).save()
Order order = new Order(3).save()
when:
service.testfun(2,3) == result
then:
2*User.findByUserId(2) >> Mock(User)
1*Order.findByUserAndId(_ as User, 1)>> Mock(Order)
result == true
}
}
However, I think that I have to mock the order.orderContent and I do not know how to mock it. Right now the test fails, because the orderContent is null so the testfun returns false.
Can anyone help me on that?
There are several things going on here, and hopefully fixing them will help you get the test running and passing.
I can't recall for certain, but I believe GroovySpy is an old feature, that isn't used with Spock tests. Instead of using that to mock a domain class, you should be using the #Mock annotation before the class definition, to specify which domain classes you would like to mock.
While you can mock the domain classes, you'll also need to actually populate the in-memory database with those objects, either in a setup: block, or in a setup() method, if they are needed for multiple tests.
You mention creating mocks, and using the Spock Mock() method will create a mock of that object, but you don't want to use that for domain objects. It is more typically used for service classes that will be manually injected into your test class.
When saving a mock domain class, I would suggest including the parameters flush: true, failOnError: true, that way any validation that fails will be indicated immediately and on the appropriate line. Without this, you can get some strange errors to occur later in the tests.
I don't know what you're doing in the when: block, but you should not be doing an assertion with == at that point, do all of those in the then: block.
Given all of this, I think you test class should look more like this:
#Mock([User, Order])
class TestFun extends Specification {
def setup() {
// This object will have an id of 1, if you want a different id, you can either specify it, or create more objects
User user = new User(first: "john", last: "doe").save(flush: true, failOnError: true)
new Order(user: user).save(flush: true, failOnError: true)
}
def "test funtest"() {
User user = new User(2).save()
Order order = new Order(3).save()
when:
boolean result = service.testfun(1, 1)
then:
// Optionally you can just use "result" on the next line, as true will be assumed
result == true
}
}
I have an action method in my controller as below
public ActionResult Index()
{
var supplier = GetSupplierForUser();
var model = SupplierService.GetOutstandingItems(supplier);
return View(model);
}
I've setup the supplier service method as
var supplierService = new Mock<ISupplierService>();
var supplier = new Supplier { Name = "Some Name",Id = 100};
supplierService.Setup(s => s.GetOutstandingItems(supplier))
.Returns(outstandingSupplierItemInfo.Object);
I don't know how can we setup the method Supplier GetSupplierForUser() which is present in the base controller to return a Supplier object. From the moq setup above a null supplier is always passed to SupplierService.GetOutstandingItems(supplier)
Any ideas? thanks
Maybe you need:
supplierService.Setup(s => s.GetOutstandingItems(It.IsAny<Supplier>()))
.Returns(outstandingSupplierItemInfo.Object);
The It.IsAny<> stuff will make your Setup match any incoming object (argument).
Since you use a loose mock, if no Setup matches the arguemnts in question, Moq will just return null. Consider using MockBehavior.Strict to have an exception instead.
If you do not use It.IsAny<>, Moq will have to try to see if the supplier that is passed to Moq, "is equal to" the supplier you used when you made the Setup. Here it can become important what .Equals(...) semantics your type (class or struct) Supplier has.
If you do not have the relevant Equals semantics, but still want to restrict the Setup to a particular situation, try this instead:
supplierService
.Setup(s => s.GetOutstandingItems(It.Is((Supplier s) => s.Name == "Some Name" && s.Id == 100)))
.Returns(outstandingSupplierItemInfo.Object);
So, I have a base class that I want to extend most (but not all) of my domain classes from. The goal is that I can add the six audit information columns I need to any domain class with a simple extends. For both creation and updates, I want to log the date, user, and program (based on request URI). It's using a Grails service (called CasService) to find the currently logged on user. The CasService then uses Spring Security and a database call to get the relevant user information for that field.
The trouble is, if I do this, then I'm going to have to Mock the CasService and request object in any unit test that tests a domain that uses these classes. That will also impact unit tests for services and controllers that use these domains. That's going to make unit testing a bit of a pain, and increase boiler plate code, which is what I was trying to avoid.
I'm fishing for better design options, and I'm open to suggestion. My current default is to simply add the same boiler plate to all my domain classes and be done with it. See below for source code and what I've tried so far.
Source
Common Audit Domain Class
package com.mine.common
import grails.util.Holders
import org.springframework.web.context.request.RequestContextHolder
class AuditDomain implements GroovyInterceptable {
def casService = Holders.grailsApplication.mainContext.getBean('casService')
def request = RequestContextHolder?.getRequestAttributes()?.getRequest()
String creator
String creatorProgram
String lastUpdater
String lastUpdateProgram
Date dateCreated
Date lastUpdated
def beforeValidate() {
beforeInsert()
beforeUpdate()
}
def beforeInsert() {
if (this.creator == null) {
this.creator = casService?.getUser() ?: 'unknown'
}
if (this.creatorProgram == null) {
this.creatorProgram = request?.requestURI ?: 'unknown'
}
}
def beforeUpdate() {
this.lastUpdater = casService?.getUser() ?: 'unknown'
this.lastUpdateProgram = request?.requestURI ?: 'unknown'
}
static constraints = {
creator nullable:true, blank: true
lastUpdater nullable:true, blank: true
creatorProgram nullable:true, blank: true
lastUpdateProgram nullable:true, blank: true
}
}
CasService
package com.mine.common
import groovy.sql.Sql
class CasService {
def springSecurityService, sqlService, personService
def getUser() {
if (isLoggedIn()) {
def loginId = springSecurityService.authentication.name.toLowerCase()
def query = "select USER_UNIQUE_ID from some_table where USER_LOGIN = ?"
def parameters = [loginId]
return sqlService.call(query, parameters)
} else {
return null
}
}
def private isLoggedIn() {
if (springSecurityService.isLoggedIn()) {
return true
} else {
log.info "User is not logged in"
return false
}
}
//...
}
What I've Tried
Creating a Test Utilities Class to do the setup logic
I've tried building a class like this:
class AuditTestUtils {
def setup() {
println "Tell AuditDomain to sit down and shut up"
AuditDomain.metaClass.casService = null
AuditDomain.metaClass.request = null
AuditDomain.metaClass.beforeInsert = {}
AuditDomain.metaClass.beforeUpdate = {}
}
def manipulateClass(classToTest) {
classToTest.metaClass.beforeInsert = {println "Yo mama"}
classToTest.metaClass.beforeUpdate = {println "Yo mamak"}
}
}
And then calling it in my Unit Test's setup() and setupSpec() blocks:
def setupSpec() {
def au = new AuditTestUtils()
au.setup()
}
OR
def setupSpec() {
def au = new AuditTestUtils()
au.manipulateClass(TheDomainIAmTesting)
}
No dice. That errors out with a NullPointerException on the CasService as soon as I try to save the domain class that extends the AuditDomain.
java.lang.NullPointerException: Cannot invoke method isLoggedIn() on null object
at com.mine.common.CasService.isLoggedIn(CasService.groovy:127)
at com.mine.common.CasService.getPidm(CasService.groovy:9)
at com.mine.common.AuditDomain.beforeInsert(AuditDomain.groovy:26)
//...
at org.grails.datastore.gorm.GormInstanceApi.save(GormInstanceApi.groovy:161)
at com.mine.common.SomeDomainSpec.test creation(SomeDomainSpec:30)
I'm open to alternate ways of approaching the issue of DRYing the Audit Information out my Domains. I'd settled on inheritance, but there are other ways. Traits aren't available in my environment (Grails 2.3.6), but maybe that's just a reason to get cracking on updating to the latest version.
I'm also open to suggestions about how to test these domains differently. Maybe I should have to contend with the audit columns in the unit tests of every domain class that has them, though I'd rather not. I'm okay with dealing with that in integration tests, but I can unit test the AuditDomain class easily enough on its own. I'd prefer that unit tests on my domains tested the specific things those domains bring to the table, not the common stuff that they all have.
So, ultimately, I've gone with Groovy delegation to meet this need.
The validation logic all lives in
class AuditDomainValidator extends AuditDomainProperties {
def domainClassInstance
public AuditDomainValidator(dci) {
domainClassInstance = dci
}
def beforeValidate() {
def user = defaultUser()
def program = defaultProgram()
if (this.creator == null) {
this.creator = user
}
if (this.creatorProgram == null) {
this.creatorProgram = program
}
this.lastUpdater = user
this.lastUpdateProgram = program
}
private def defaultProgram() {
domainClassInstance.getClass().getCanonicalName()
}
private def defaultUser() {
domainClassInstance.casService?.getUser() ?: 'unknown'
}
}
I created this abstract class to hold the properties while trying various solutions. It could probably be folded into the validator class with no problems, but I'm just lazy enough to leave it in there since it's working.
abstract class AuditDomainProperties {
String creator
String creatorProgram
String lastUpdater
String lastUpdateProgram
Date dateCreated
Date lastUpdated
}
And finally, here's how to implement the validator class in a Grails domain class.
import my.company.CasService
import my.company.AuditDomainValidator
class MyClass {
def casService
#Delegate AuditDomainValidator adv = new AuditDomainValidator(this)
static transients = ['adv']
//...domain class code
static mapping = {
//..domain column mapping
creator column: 'CREATOR'
lastUpdater column: 'LAST_UPADTER'
creatorProgram column: 'CREATOR_PGM'
lastUpdateProgram column: 'LAST_UPDATE_PGM'
dateCreated column: 'DATE_CREATED'
lastUpdated column: 'LAST_UPDATED'
}
}
This approach doesn't work perfectly for Unit and Integration tests, it seems. Trying to access the dateCreated column in either fails with an error that there is no such property for the domain class in question. Thats odd, since running the application works fine. With the Unit tests, I would think it was a mocking issue, but I wouldn't expect that to be a problem in the integration tests.
I was trying to write a unit test case for the action 'passwordActive' in 'LoginController'. But i am not sure which all things to be mocked and how to do that.
def passwordActive={
log.error("Entering into Password Active")
println "Password Active -- Params = "+params
String contextPath = request.contextPath
String username = null
User user = null
try{
if (params.username != null && params.username.trim().length() > 0) {
username = params.username
user = User.findByUsername(username)
}
if (user == null) {
log.error("USER not found.")
flash.errorLogin = "Username is incorrect"
redirect(action:"auth")
return
}else if(user.password != params.password){
log.error("USER password is wrong..")
flash.errorLogin = "Please enter valid password.."
redirect(action:"auth")
return
}else if (!user.active) {
log.error("USER is inactive.")
flash.errorLogin = "User in Inactive"
redirect(action:"auth")
return
}
session["userid"] = user.id
String userRole = user.authority.authority
String url = contextPath + "/" + userRole
println "URL = "+url
redirect(url: url, method: 'post', params: params)
return
}
catch(Exception e){
println e.printStackTrace()
}
log.error("Exit into Password Active")
}
i want to write a test case where username does not exist. in that case flash message should be having the message "username incorrect" and i can assert it.
Here what all things should be mocked and how to do that. Could you please explain the working with a small example (preferably related with above code so that it would be easy for me to understand)
Writing your own security implementation is a terribly bad idea. I suggest you take a look at how the Spring Security plugin does it for educational purposes, then just use that plugin - it is mature, flexible and easy to set up.
For said educational purposes, let's see how we could mock above code using an approach that is loosely along the lines of how Spring Security does it: You would move the user lookup into a service/bean as controllers aren't meant to contain any 'business' logic. Say something like this:
class UserProviderService {
User findUser(String username) {
User.findByUsername(username)
}
}
In the controller you would injected it as a dependency (def userProviderService) on which you would then call your own function, e.g. userProviderService.findUser(username). During normal operation Grails would inject your UserProviderService into the controller while in unit tests you can mock it however you like (e.g. controller.userProviderService = [ findByUsername: { -> return null } ] if you for example wanted to test the controller response for an unknown user.
Similar for password comparison you would use a passwordService that would implement password hashing, comparison and so on and you could mock its response before every test. Note that this stuff is again not trivial if you care about security and very easy to mess up so again: don't do it yourself!
The way you structure your code determines how easy or hard it will be to test it. Sure you could mess with User.metaclass and override methods left and right, but that approach usually proves very fragile. By splitting up your code you can make your life easier! How to structure your code so it is testable is definitely something you learn by experience so keep thinking about it!