Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mock Service | Grails 5.3.3 #13506

Open
urmichm opened this issue May 6, 2024 · 10 comments
Open

Mock Service | Grails 5.3.3 #13506

urmichm opened this issue May 6, 2024 · 10 comments

Comments

@urmichm
Copy link

urmichm commented May 6, 2024

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

  1. Grails 5.3.3
  2. 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

@urmichm
Copy link
Author

urmichm commented May 6, 2024

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.

osscontributor added a commit to osscontributor/issue13506 that referenced this issue May 8, 2024
@urmichm
Copy link
Author

urmichm commented May 10, 2024

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.

@osscontributor
Copy link
Member

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

@osscontributor
Copy link
Member

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.

@urmichm
Copy link
Author

urmichm commented May 16, 2024

Maybe let me paraphrase the question. @osscontributor how do you mock or configure DataSource for unit tests?

@osscontributor
Copy link
Member

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.

@urmichm
Copy link
Author

urmichm commented May 22, 2024

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)

@osscontributor
Copy link
Member

@urmichm Can you share a link to a sample project which demonstrates the problem?

@puneetbehl
Copy link
Contributor

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:

  1. Suppose you have a BookController.groovy which inject a service named BookService as:

    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
            }
        }
    }
  2. 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
            }
        }
    }
  3. Ensure that the DataSource is properly configured in application.yml or application.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
  4. When you write the unit tests for BookController.groovy then you might mock the BookService and 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
        }
    }
  5. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants