Docs say we can test controllers with command objects just by mocking params
http://grails.org/doc/latest/guide/testing.html#unitTestingControllers
I wonder if this works for Nested Command Objects? Has anyone gotten this to work?
Example:
Controller
def create(FormCommand form){
form.validate()
...
}
Command
class FormCommand {
InnerCommand cmd
}
class InnerCommand{
String x
static constraints ={
x(nullable: false)
}
}
Test
void testCreate(){
params["inner.x"]="any"
controller.create()
...
}
My expectation is that the command objects are created and data binding works, also I expect inner command to be validated. Am I expecting too much?
Ok, it seems that's possible what you want, but needs some code :-)
Data Binding
For nested command objects Grails databinding needs a not null instance of the inner command.
To do that, you can create a custom org.codehaus.groovy.grails.web.binding.BindEventListener:
class InnerCommandBindEventListener împlements BindEventListener {
public void doBind(Object target, MutablePropertyValues source, TypeConverter typeConverter) {
target.cmd = new InnerCommand()
}
}
And declare it in your resources.groovy
innerCommandBindEventListener(InnerCommandBindEventListener)
Nested Validation
To resolve the validation issue, you need a custom validator for your cmd:
class FormCommand {
InnerCommand cmd
static constraints = {
cmd nullable: false, validator: { cmd, obj ->
// manually trigger the inner command validation
if(!cmd.validate()) {
return 'invalid.innercommand.message'
}
}
}
}
Not sure if exactly applicable to the issue at hand, but for at least the data binding in tests for controller actions this works:
params.'player1.name' = "John"
Where as this gives null pointer exception:
params.player1.name = "John"
When controller is doing:
Game game = new Game(params)
And Game has:
Player player1
Related
I'm using Grails 2.3.7. I have a simple Person domain object:
class Person {
String name
static constraints = {}
}
And controller:
#Transactional(readOnly = true)
class PersonController {
def index() {
render view:'index', model:[personList:Person.list()]
}
def show(Long id) {
def person = Person.get(id)
render view:'show', model:[person:person]
}
...
}
I'm trying to write a unit test for the controller to exercise the show method, but I am not sure how I need to configure the test to ensure that the static call Person.get(id) returns a value.
Here's my (failing) attempt:
import grails.test.mixin.*
import spock.lang.*
#TestFor(PersonController)
#Mock(Person)
class PersonControllerSpec extends Specification {
void "Test show action loads Person by id and includes that person in the model rendered"() {
setup:
def personMockControl = mockFor(Person)
personMockControl.demand.static.get() { int id -> new Person(name:"John") }
when:"A valid person id passed to the show action"
controller.show(123)
then:"A model is populated containing the person domain instance loaded by that unique id"
model.person != null
model.person.name == "John"
}
}
This test fails because the condition model.person != null fails. How do I rewrite this test in order to make it pass?
EDIT
In this case I can side-step the static method call by reworking the test thusly:
void "Test show action loads Person by id and includes that person in the model rendered"() {
setup:
def p = new Person(name:"John").save()
when:"A valid person id passed to the show action"
controller.show(p.id)
then:"A model is populated containing the person domain instance loaded by that unique id"
model.person != null
model.person.id == p.id
model.person.name == "John"
}
However, I'd really like to understand how to correctly mock the Person's static get method.
EDIT 2
Here's another situation to demonstrate my perceived need to mock the get method. Given this filter code:
def fitlers = {
buyFilter(controller:"paypal", action:"buy") {
after = {
new VendorPayment(payment:request.payment, vendor: Vendor.get(params.buyerId)).save()
}
}
}
I'm trying to test that this filter works as expected using the following test:
void "test the buyFilter to ensure it creates a VendorPayment when the PayPal plugin controller is called" () {
setup:
// how do I mock the Vendor.get(params.buyerId)
// to return a vendor in order to save a new VendorPayment
when:
withFilters(action:"buy") {
controller.buy()
}
then:
VendorPayment.count() == 1
}
Like dmahaptro mentioned, you do not need to mock your person. The preferred/proper way of grabbing an object in a test via .get() is the way you have it--you create the domain instance, and pass its id to the controller action for consumption.
However, if you need to mock a static method like this there are two approaches:
1) Use the mockFor method provided by GrailsUnitTestMixin and then specify the static method like so:
GrailsMock mockPerson = mockFor(Person)
mockPerson.demand.static.get() { Long id -> new Person(name:"John") }
Note that in your case, the mock's static get method did not contain the correct parameter type (int was used instead of Long) so this method was not being invoked.
2) Modify the class' metaClass. Changing the metaClass is highly discouraged (especially when the #Mock annotation already sets everything up for you), and can lead to test leakage (i.e., other tests will not work properly after you've changed the metaClass, unless you restore the original metaclass after the test is done).
That said, for the sake of familiarity, the way you would mock the .get() method is via:
Person.metaClass.static.get = { Long id -> return personWithId }
Again, this is considered bad practice and should be avoided, but there you have it.
I am trying to test the override of equals in a domain object to ensure that 'contains' works. The unit test mechanics are defying me, in spite of documentation and bugs saying that I should be able to mock addTo.
My test is:
#TestFor(Member)
#Mock([Member])
class MemberCategoryTests {
void testContains() {
MemberCategory schoolCat = new MemberCategory(name: "SCHOOL")
MemberCategory membersCat = new MemberCategory(name: "Members")
Member member = new Member(membershipNumber: "333333",
surname: "Tester",
forenames: "Jim",
preferredEmail: "mmm#yyy.com")
member.addToMemberCategories(schoolCat)
member.addToMemberCategories(membersCat)
MemberCategoryRedback memberCategoryRedback = new MemberCategoryRedback(name: "SCHOOL")
assert member.memberCategories.contains(memberCategoryRedback)
}
}
The error is:
No signature of method: au.com.interlated.civiLink.Member.addToMemberCategories() is applicable for argument types: (au.com.interlated.civiLink.MemberCategory)
The domain object isn't special. MemberCategory implements equals.
This document says #Mock([yyy]) should do the trick: Naleid upgrading to grails 2 testing as does unit testing addto
I feel you need two changes to make it work
1.Add MemberCategory to #TestFor, because your trying to add members to membercategirt then your code will become like this:
#TestFor(MemberCategory)
#TestFor(Member)
2.Also call save() after adding member.addToMemberCategories(membersCat), then your code will become some thing like this
if(!member.save(validate: true,flush:true,failOnError: true)) {
member.errors.each {
log.debug(it)
}
}
Hope this helps.
I am trying to write unit test case for grails controller which has following structure:
class MyController{
def save(){
def myDomain = new MyDomain(params)
business validation 1
business validation 2
myDomain.writedatasource.save()
business validation 3
business validation 4
}
}
Since Unit test doesn't load Datasource.groovy the writedatasource isn't available during unit testing so the test cases for "business validation 3" and "business validation 4" fail as I get
groovy.lang.MissingPropertyException: No such property: writedatasource for class: MyDomain
How can I modify my test case to test validation scenarios 3 and 4?
Test case is simple and looks like follows:
void testSave(){
...setup...
controller.save()
assert conditions
....
}
Not sure if that could make the trick, but you can try:
def 'the controller call the save method in writedatasource'() {
given:
def calls = 0
and:
def myDomainInstance = new MyDomain()
and:
MyDomain.metaClass.getWritedatasource = { new Object() }
and:
Object.metaClass.save = { Map attrs ->
calls++
}
when:
controller.save()
then:
calls == 1
}
But the only thing are you doing is testing that the save is called under writedatasource, so it would be better to have also an integration test. If that makes the trick, please answer as well at http://grails.1312388.n4.nabble.com/Mocking-in-a-unit-test-a-domain-object-multiple-datasources-td4646054.html as they seem to have the same problem.
Addressing original question:
I ran into this and would have had to mock many, many more methods than just 'save' on multiple domains. So instead, I overrode the getter of my domains to simply return the invoking domain instance:
def setup() {
[MyDomain1, MyDomain2].each { Class clazz ->
mockDomain(clazz)
clazz.metaClass.getWritedatasource = {
return delegate
}
clazz.metaClass.'static'.getWritedatasource = {
return delegate
}
}
}
This also saves me from including #DirtiesRuntime since I'm not updating the metaClass of anything I'd want to clean.
Most importantly, whatever calls the datasource, be it the domain class or the instance, it should be decorated with GORM fanciness from mockDomain, meaning I don't have to mock any of the GORM methods.
What if you want dynamic datasources?
In my particular case, datasources are configurable and may change depending on environment. If you're in a similar situation, you can configure this in your domain mapping:
static mapping = {
datasources Holders.grailsApplication?.config.dynamic?.datasources
...
}
where dynamic.datasources is an array of datasource names. Then, in the test setup:
def setup() {
grailsApplication.config.dynamic.datasources = ['foo', 'bar']
[MyDomain1, MyDomain2].each { Class clazz ->
mockDomain(clazz)
grailsApplication.config.dynamic.datasources.each{
clazz.metaClass."get${it.capitalize()}" = {
return delegate
}
clazz.metaClass.'static'."get${it.capitalize()}" = {
return delegate
}
}
}
}
Im testing with PHPUnit and my test fails on a function. But i don't know why.
The function i want to mock:
public function subscribe($email)
{
$message = new SubscribeMessage();
$message->setEmailaddress($email);
$message->setLocale(Locale::getDefault());
$this->getAmqpProducer()->publish($message, 'newsletter-subscribe');
return true;
}
and my Unit test:
public function testSubscribeSendsAmqpMessage()
{
$email = 'email#email.nl';
$locale = 'nl';
$this->amqpProducerMock
->shouldReceive('publish')
->once()
->with(
\Mockery::on(
function ($message, $routingkey) use (&$publishedMessage) {
$publishedMessage = $message;
return $routingkey == 'newsletter-subscribe';
}
)
);
$this->service->subscribe($email, $locale);
}
but the test says:
Mockery\Exception\NoMatchingExpectationException : No matching handler found for AcsiRabbitMq\Producer\Producer::publish(AcsiNewsletter\RabbitMq\Message\SubscribeMessage, "newsletter-subscribe"). Either the method was unexpected or its arguments matched no expected argument list for this method
How can i fix my Unit test? Or how can i refactor my test?
You Mock the subscribe, not the internal publish. When you run the test and call ->subscribe, it will attempt to execute the code in the class. Therefore, it will try to run the subscribe() method, which you appear to have a strange reference to your Mock.
Normally, your test will mock the subscribe, so you can return a value for the assert test, which is hard coded.
You appear to have tried to mock the GetAmqpProducer() object that is in your regular code. You need to either be able to pass the mock object to be used into your class, or to be able to assign it.
Simplified Example:
class Email
{
private $MsgObject;
// Constructor Injection
public __construct(SubscribeMessage $MessageObject)
{
$this->MsgObject = $MessageObject;
...
}
// Setter Injection
public function SetSubscribeMessage(Subscribe $MessageObject)
{
$this->MsgObject = $MessageObject;
}
public function setEmailaddress($email)
{
$this->MsgObject->emailAddress = $email;
...
}
public function setLocale($Locale)
{
$this->MsgObject->Locale = $Locale;
...
}
...
}
Your class sample above has too many internal objects and dependencies to be tested as such, since the test will actually call these. You would use Dependency Injection to pass the objects with known state, and have them return properly.
Please note, I am not showing how to do this in Mockery, as I do not use it, but this simple example should help you understand what I am trying to express.
So a simple test might look like:
public function testSubscribeMessage()
{
$email = 'email#email.nl';
$this->Mock(
->shouldReceive('setEmailAddress')
->once()
->will_return($email)
);
$SubscribeMessage = new SubscribeMessage($this->Mock);
$SetEmail = $SubscribeMessage->setEmailAddress($email);
$this->assertEquals($email, $SetEmail);
}
In my grails application, in the controller, I use following kind of a thing :
class SampleController {
def action1 = {
def abc = grailsApplication.getMetadata().get("xyz")
render abc.toString()
}
}
While running the app, it correctly reads the property "xyz" from application.properties and works fine. But when I write a unit test case for the above controller as follows :
class SampleControllerTests extends ControllerUnitTestCase {
SampleController controller
protected void setUp() {
super.setUp()
controller = new SampleController()
mockController(SampleController)
mockLogging(SampleController)
}
void testAction1() {
controller.action1()
assertEquals "abc", controller.response.contentAsString
}
}
But when I do "grails test-app", I expect that it will pick up the property "xyz" from application.properties and will return as expected. But it gives error as "No such property : grailsApplication".
I understand, I guess I need to mock grailsApplication object and I tried many of the options also But all those didn't work.
I am new to Grails.
mockController will not mock the GrailsApplication, you will need to do it yourself.
The fastest solution would be something like:
protected void setUp() {
super.setUp()
mockLogging(DummyController)
GrailsApplication grailsApplication = new DefaultGrailsApplication()
controller.metaClass.getGrailsApplication = { -> grailsApplication }
}
This solution is not perfect - it will create a new DefaultGrailsApplication during each setup and mockController also creates some additional instances of DefaultGrailsApplication.
Note that you do not need to call mockController by yourself it will be done by the ControllerUnitTestCase for you.