This thread describes how to call a Service in a view: How do I get an instance of a Grails service programmatically?
This describes how to call it in a Servlet: How do I get an instance of a Grails service programmatically?
This one says how to call it in a Controller: How to dynamically select a service in Grails
I need to get a handle to my service in an integration test. Is this possible?
If its an integration test, you have access to the entire runtime so just inject it like you do normally.
def someService
Have a look at Testing Controllers with Service.
Gist:
You have to initialize the service (spring bean) to controller in the test.
class FilmStarsTests extends GroovyTestCase {
def popularityService
void testInjectedServiceInController () {
def fsc = new FilmStarsController()
fsc.popularityService = popularityService
fsc.update()
}
}
Services are autowired in integration tests as in controllers.
Related
Let's say I have some codes to make 2 rest calls to the other API. And I need a service layer to make 2 rest calls in the same action.
In Java, I probably would do something like this
#Service
public class RestService{
#Autowired
RestClient restClient
def shutdown(){
if(restClient.isSystemGood()){
restClient.shutdownSystem()
}
}
}
#Repository
public class RestClient {
boolean isSystemGood() {
...
}
void shutdownSystem() {
...
}
}
How should I fit in grails way to do the similar thing?
Should I put the logic inside RestClient into a domain class? or Should I put both RestService and RestClient into a domain class? Does domain class have to be backed by a DB?
Because I only see service and domain folders in the grails default file structure.
RestService (in your simple case a non-transactional one) inside grails-app/services directory is the right place for the logic. Inject the service inside the controller action to use it.
Injecting services into the domain class is not recommended. It hard to test, degrades the read performance and results in a spaghetti design. This is why Grails now by default, has disabled the services injection in the domain class
I'm running into an issue where a unit test that extends HibernateSpec is failing due to the sessionFactory not being injected into the service under test. Whenever a method on the sessionFactory is called during the test, I get a NullPointerException (e.g. java.lang.NullPointerException: Cannot invoke method getClassMetadata() on null object) and the test subsequently fails.
I'm using Grails 3.2.4 and Hibernate 5.
This was working when the test used #TestMixin(HibernateTestMixin), but it looks like with some updates, the mixin is deprecated and suggests using HibernateSpec instead.
Here's a snippet from my test:
class TestDatabaseServiceSpec extends HibernateSpec {
def setup() {
}
def cleanup() {
}
void "test method"() {
when:
service.method()
then:
true
}
}
And here is a snippet from the service under test:
void method() {
...
TABLE_NAMES.add(sessionFactory.getClassMetadata('MyDomain').tableName)
...
}
I have tried to set service.sessionFactory in setup method as well as setupSpec method with the sessionFactory available in the test, but that did not help unfortunately. I have thought about using an integration test, but I would really like to see if I can continue to have this unit test work as it was before. Does anyone know if I am I doing something incorrectly or if there is a workaround/solution for this?
Thank you!
Viewing source code of HibernateSpec could give some hint. Could be there is no getSessionFactory() method in Hibernate 5 grails plugin that you are using.
I am using org.grails.plugins:hibernate5:6.0.6 with grails 3.2.5 and sessionFactory is not null for me in unit tests.
I understand that it is not always possible to upgrade, but it could give an idea what is the source of the problem.
Update:
service.sessionFactory = sessionFactory
also must be called in test setup.
What worked for me was to add the annotation #FreshRuntime to the spec and to add a doWithSpring closure to the test that sets up the sessionFactory for me (i.e. sessionFactory(InstanceFactoryBean, sessionFactory, SessionFactory)). Also, I made sure to add both service.sessionFactory = sessionFactory and service.transactionManager = transactionManager to the setupSpec method. Finally, I needed to override getDomainClasses() to work with all of the domain objects I wanted to in the test. Thanks for all the help!
The Test Mixins have been deprecated for the new traits since 3.2.3.
https://docs.grails.org/latest/guide/testing.html
Since Grails 3.3, the Grails Testing Support Framework is used for all
unit tests. This support provides a set of traits.
However, I still find myself using HibernateSpec as I can get multiple Domain classes from that, and the ServiceUnitTest for the service as well. The HibernateSpec has the needed sessionFactory.
E.g.
class MyServiceSpec extends HibernateSpec implements ServiceUnitTest<MyService>{
List<Class> getDomainClasses() { [Tag, TagLink, TagLinkValue, TagValue, TestDomain] }
...
def setup() {
service.sessionFactory = sessionFactory
...
}
// tests here with all those domain classes available and the service
}
The DataTest allows for multiple domain classes, but not the service as well.
https://testing.grails.org/latest/guide/index.html#unitTestingDomainClasses
the service test allows for the service, but not multiple domain classes.
https://testing.grails.org/latest/guide/index.html#unitTestingServices
You could use both of them together, but neither one of them has the session factory (that I could see), I'm on Grails 4.0.1. I looked through Github code for the latest and still don't see it available. Plus, the DataTest doesn't a hibernate session, it uses a SimpleMap instead so you can't use hql with it, which I wanted.
My Service was using the statelessSession
def statelessSession = sessionFactory.openStatelessSession()
String queryString = "select DISTINCT(t.tagRef) from com.fmr.aps.taggable.TagLink t group by t.tagRef order by t.tagRef ASC"
Query query = statelessSession.createQuery(queryString)
query.setReadOnly(true);
ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
So I needed a unit test trait or base class with it and I spent several hours trying to make the new traits give me what I wanted. Even I began subclassing the DataTest class to try and make it give me the sessionFactory, but eventually just went with the HibernateSpec and it's nice to be able to run that one test class with several Domains, the Service and the sessionFactory.
With just simple following controller action spock integration-test. Here is my Test.
#Integration
#Rollback
class TestControllerSpec extends Specification {
def setup() {
}
def cleanup() {
}
void "test something"() {
setup:
def c = new TestController()
c.index()
expect:
c.response.contentType !=null
}
}
getting following Exception
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at grails.web.api.WebAttributes$Trait$Helper.currentRequestAttributes(WebAttributes.groovy:45)
at grails.web.api.ServletAttributes$Trait$Helper.getRequest(ServletAttributes.groovy:42)
I've been doing this and it seems to work fine:
Add field:
#Autowired
WebApplicationContext ctx
In setup():
GrailsWebMockUtil.bindMockWebRequest(ctx)
In cleanup():
RequestContextHolder.resetRequestAttributes()
Unfortunately it might be a limitation in Grails 3, that you cannot use integration tests to test controllers.
To integration test controllers it is recommended you use
create-functional-test command to create a Geb functional test.
Source from Grails documentation
This seems to be a major change of direction from previous versions of grails. If you really need to test a controller in an integration test, you could try doing this:
NOTE: I realize this might be a bad practice, and it goes against Grails documentation, but sometimes you also need to test things more programmatically, where unit tests aren't sufficient, and are Geb tests aren't granular enough.
#TestFor(TestController) // This will provide a mocked "controller" reference
#Integration
#Rollback
class TestControllerSpec extends Specification {
// If TestController uses any services, have them autowired into this test
#Autowired
SomeService someService
def setupSpec() {
// Now connect those services to the controller
controller.someService = someService
}
void "test something"() {
when:
controller.index()
then:
response.contentType != null
}
}
WARNING: After some additional work with this format I did find a problem. Using #TestFor will call Holders.clear() when it is complete, which means that there will not be a grailsApplication object in Holders. This will cause problems if you have any integration tests that run after one that uses the approach above. After much digging, it doesn't look like there is an easy (or even hard) way of making this work, which is possibly why it is not supported in Grails 3. That being said, one option is to mark other integration tests with #TestFor, so that the Holders class will be properly populated. Is this a hack? Yes it is! You will need to decide if it is worth the effort of adding this overhead to all tests. In my case it was only one other integration test that needed this (as it is a small application), but if it was more than that I would not use this approach.
I have a service that needs the scope "session". So I simply use
class MyService {
static scope = "session"
...
}
But in my integration test it doesn't get wired correctly:
class MyServiceIntegrationSpec extends IntegrationSpec {
def myService
...
}
I always get the error:
Error creating bean with name 'myService': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton;
What am I doing wrong?
You cannot inject 'lower' scoped bean into singleton. It's like you would like to inject controller into service. Controller is created with each request, but service is one for the application (if singleton by default). Here you can have different sessions, but only one singleton for test - so service from which session would you like to use in test?
It can be done getting the bean from the context instead of injecting it:
def grailsApplication
void testSomething() {
given:
def myService = grailsApplication.mainContext.getBean('myService')
...
Changing the scope of the test from singleton to session would also resolve the problem, but I don't know if this is possible.
I have a Service, called Mailer, and a controller, which should use this service:
class DocumentController {
def mailer
def publish = {
mailer.sendReport()
}
}
But when I call publish I get:
java.lang.NullPointerException: Cannot invoke method sendReport() on null object
Why doesn't the dependency injection work here?
Grails is all about conventions. I think mailer needs to be called mailerService. It needs to be in the services directory. Controller needs to be in the controllers directory.
From the documentation
"A service contains business logic that can be re-used across a Grails application. In Grails a service is a class that ends in the convention "Service" and lives in the grails-app/services directory. A service can be created with the create-service command:"