grails-core
grails-core copied to clipboard
Mock Service | Grails 5.3.3
Expected Behavior
When running unit test on a controller
class MyControllerTest extends Specification implements ControllerUnitTest<MyController>, DataTest
I need to run unit tests for the controller
Actual Behaviour
Exception
NoSuchBeanDefinitionException: No qualifying bean of type 'absolutely.unrelated.Service'
Steps To Reproduce
- Grails 5.3.3
- Multiple contollers and services
"org.grails:grails-web-testing-support:3.1.2"
"org.grails:grails-gorm-testing-support:3.1.2"
Environment Information
Ububntu java 1.8
Example Application
No response
Version
5.3.3
Similar issue is about DataSource.
NoSuchBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected at least 1 bean which qualifies as autowire candidate.
Data source is a nested bean far down the hierarchy of beans. Running a Unit Test, i am just expecting a UNIT peace of code to be tested. No need for data source or loading all the beans of the application.
@urmichm Does the test at https://github.com/osscontributor/issue13506/blob/25d620dd85b666ba5c0a3b9ad194dcabc739ac63/src/test/groovy/issue13506/MyControllerSpec.groovy pass for you?
Hello, unfortunately no.
I am getting an exceptions during the initializationError that some other Beans are not created.
example: NoSuchBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected at least 1 bean which qualifies as autowire candidate.
The DataSource is required in an unrelated Service which is in some other unrelated Controller.
Hello, unfortunately no.
That is interesting.
Are you using Gradle to run the test?
This is what I am seeing: https://gist.github.com/osscontributor/1786f4cce7e73aa943f3f6fc479fc4b4
The DataSource is required in an unrelated Service which is in some other unrelated Controller.
I have tried a few combinations of class dependencies and I haven't yet been able to reproduce the problem. If you could send a PR to the repo I linked above with a combination of things that recreates the problem, that would prove helpful in troubleshooting.
Maybe let me paraphrase the question. @osscontributor how do you mock or configure DataSource for unit tests?
how do you mock or configure DataSource for unit tests?
If you are using GORM, you generally wouldn't. The more common thing is to kind of mock GORM using the default implementation that is configured in unit tests which uses a Map backed store. It is unusual for a controller to interact directly with a DataSource.
If you can provide a sample app which demonstrates what you are trying to test I would be happy to send you a PR or identify if there is relevant bug in the framework.
Thank you for the feedback.
Hello @osscontributor
The controller does not interact with a DataSource. The other controller has a service which has a bean which has a DataSource bean. I do not understand why the other controller and all its hierarchy of the beans is created. I do not need it. I just need a unit test for MyController. My question is about mocking the beans i do not need. Especially mocking a DataSource bean from a random Bean of a random Service from an unrelated Controller.
I the unit tests I want to make the DataSource is not required at all, since as you mentioned DataSource is not there (at least directly)
@urmichm Can you share a link to a sample project which demonstrates the problem?
I assume that it might be that you are calling another controller from the one under unit test. But, if could share the code which is unit tested here or better a sample application that'd be helpful to debug the issue.
Generally, you would mock any service which is being called from the controller. Let's walk through an example:
-
Suppose you have a
BookController.groovywhich inject a service namedBookServiceas:grails-app/controllers/com/example/BookController.groovy
package com.example import grails.rest.RestfulController class BookController extends RestfulController<Book> { BookService bookService BookController() { super(Book) } def show(Long id) { def book = bookService.getBookById(id) if (book) { respond book } else { render status: 404 } } } -
Here is the code from BookService:
grails-app/services/com/example/BookService.groovy
package com.example import groovy.sql.Sql import javax.sql.DataSource class BookService { DataSource dataSource Book getBookById(Long id) { Sql sql = new Sql(dataSource) def row = sql.firstRow('SELECT * FROM book WHERE id = ?', [id]) if (row) { new Book(row) } else { null } } } -
Ensure that the DataSource is properly configured in
application.ymlorapplication.groovy. Grails typically auto-configures the DataSource based on the environment.grails-app/config/application.yml
dataSource: pooled: true driverClassName: org.h2.Driver username: sa password: '' dbCreate: update url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE environments: development: dataSource: dbCreate: update url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE test: dataSource: dbCreate: update url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE production: dataSource: dbCreate: update url: jdbc:h2:file:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE -
When you write the unit tests for
BookController.groovythen you might mock theBookServiceand test controller as:package com.example import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class BookControllerSpec extends Specification implements ControllerUnitTest<BookController> { def bookService = Mock(BookService) void setup() { controller.bookService = bookService } void "test show action with existing book"() { given: def book = new Book(title: "Grails in Action") bookService.getBookById(1L) >> book when: controller.show(1L) then: response.status == 200 response.json.title == "Grails in Action" } void "test show action with non-existing book"() { given: bookService.getBookById(1L) >> null when: controller.show(1L) then: response.status == 404 } } -
When writing a unit test for BookService, you might mock the DataSource and test the service logic.
package com.example import grails.testing.services.ServiceUnitTest import spock.lang.Specification import groovy.sql.Sql import javax.sql.DataSource class BookServiceSpec extends Specification implements ServiceUnitTest<BookService> { def dataSource = Mock(DataSource) def sql = Mock(Sql) void setup() { service.dataSource = dataSource } void "test getBookById with existing book"() { given: def mockRow = [id: 1L, title: 'Grails in Action'] sql.firstRow('SELECT * FROM book WHERE id = ?', [1L]) >> mockRow when: def book = service.getBookById(1L) then: 1 * dataSource.connection >> Mock(Connection) 1 * new Sql(connection) >> sql 1 * sql.firstRow('SELECT * FROM book WHERE id = ?', [1L]) >> mockRow book.title == 'Grails in Action' } void "test getBookById with non-existing book"() { given: sql.firstRow('SELECT * FROM book WHERE id = ?', [1L]) >> null when: def book = service.getBookById(1L) then: 1 * dataSource.connection >> Mock(Connection) 1 * new Sql(connection) >> sql 1 * sql.firstRow('SELECT * FROM book WHERE id = ?', [1L]) >> null book == null } }
Please note that above examples are only for reference. I didn't get the chance to verify this manually but would need to mock things as per the application and the specific services, methods, or contructors.
I assume that it might be that you are calling another controller from the one under unit test.
I believe there is no good reason to do that in a unit test.