1

The Mirconaut docs on JDBC repositories clearly tells us we have to create a test repository to test against another dialect. I think this will be manageable (e.g. Postgres for production and H2 for test).

The problem is I have to repeat my methods (e.g. find()) in the test repository. I have a book repository and a test repository:

@JdbcRepository(dialect = Dialect.POSTGRES)
interface BookRepository extends CrudRepository<Book, Long> {
  Optional<Book> find(String title);
}

@JdbcRepository(dialect = Dialect.H2)
@Replaces(bean = BookRepository)
@Requires(env = ["test"])
interface TestBookRepository extends BookRepository {
  // Optional<Book> find(String title);
  // Required to make the find() method appear in the TestBookRepository
}

To make the find() method available in the TestBookRepository, I had to repeat the method (see commented line above).

Is there a better way to avoid repeating myself? The methods from the CrudRepository interface are available in the TestBookRepository without problems. Why is the find() method not treated the same?

BTW, I don't want to mock the test repository. I want to test the repository 'logic' injected by Micronaut-Data against an SQL database.

This is for Micronaut Data 1.0.0.M5, using Groovy for the source.

Mike Houston
  • 246
  • 2
  • 8
  • Are you saying that when `BookRepository` defines `Optional find(String title)` and `TestBookRepository extends BookRepository` that the `find` method is not inherited into `TestBookRepository`? – Jeff Scott Brown Dec 02 '19 at 14:43
  • You should change the dialect (if needed, although this is going to bring you some surprises at some point) before running the application, based on the profile you are using...so the repository stays the same. – x80486 Dec 02 '19 at 19:44
  • Jeff, yes, I am saying the sub-class of the repository does no see the find() method in the sub-class. If I duplicate the find() method, then it works. If the find() method is in just the parent class, then it is not visible to the test. – Mike Houston Dec 02 '19 at 20:56
  • x80486, I really didn't want to use a remote, live database for my tests. Perhaps I need to re-think that. I just now realized that ALL of my tests will be against H2. Not a big problem, but probably not a good idea. I will investigate test speed with local Postgres. – Mike Houston Dec 02 '19 at 21:01
  • I think what you are describing would require a bug in the Java compiler. – Jeff Scott Brown Dec 04 '19 at 15:34
  • What version of Micronaut Data are you using for which the method is not inherited into the child interface? – Jeff Scott Brown Dec 10 '19 at 18:54

3 Answers3

2

To make the find() method available in the TestBookRepository, I had to repeat the method (see commented line above).

I cannot reproduce that behavior. In order for that to be the case I think the java compiler would need to have a bug in it that caused that.

See the project at https://github.com/jeffbrown/mikehoustonrepository.

https://github.com/jeffbrown/mikehoustonrepository/blob/82b8af568042c762a86cef9965e52fdc61053421/src/main/java/mikehoustonrepository/BookRepository.java

// src/main/java/mikehoustonrepository/BookRepository.java
package mikehoustonrepository;

import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;

import java.util.Optional;

@JdbcRepository(dialect = Dialect.POSTGRES)
public interface BookRepository extends CrudRepository<Book, Long> {
    Optional<Book> find(String title);
}

https://github.com/jeffbrown/mikehoustonrepository/blob/82b8af568042c762a86cef9965e52fdc61053421/src/test/java/mikehoustonrepository/TestBookRepository.java

// src/test/java/mikehoustonrepository/TestBookRepository.java
package mikehoustonrepository;

import io.micronaut.context.annotation.Replaces;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;

@JdbcRepository(dialect = Dialect.H2)
@Replaces(BookRepository.class)
public interface TestBookRepository extends BookRepository{}

https://github.com/jeffbrown/mikehoustonrepository/blob/82b8af568042c762a86cef9965e52fdc61053421/src/main/java/mikehoustonrepository/BookController.java

package mikehoustonrepository;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;

import java.util.Optional;

@Controller("/books")
public class BookController {

    private final BookRepository bookRepository;

    public BookController(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @Get("/")
    public Iterable<Book> index() {
        return bookRepository.findAll();
    }

    @Post("/{title}/{author}")
    public Book create(String title, String author) {
        return bookRepository.save(new Book(title, author));
    }

    @Get("/find/{title}")
    public Optional<Book> findByTitle(String title) {
        return bookRepository.find(title);
    }
}

https://github.com/jeffbrown/mikehoustonrepository/blob/82b8af568042c762a86cef9965e52fdc61053421/src/test/java/mikehoustonrepository/BookControllerTest.java

package mikehoustonrepository;

import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.annotation.MicronautTest;
import org.junit.jupiter.api.Test;

import javax.inject.Inject;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

@MicronautTest
public class BookControllerTest {

    @Inject
    BookClient bookClient;

    @Test
    public void testFind() throws Exception {
        Optional<Book> book = bookClient.find("The Nature Of Necessity");
        assertFalse(book.isPresent());

        bookClient.create("The Nature Of Necessity", "Alvin Plantinga");

        book = bookClient.find("The Nature Of Necessity");
        assertTrue(book.isPresent());
    }
}

@Client(value="/", path = "/books")
interface BookClient {
    @Post("/{title}/{author}")
    Book create(String title, String author);

    @Get("/")
    List<Book> list();

    @Get("/find/{title}")
    Optional<Book> find(String title);
}

That test passes.

You can see that a different repository is being used for test (TestBookRepository) that is used for other environments (BookRepository).

I hope that helps.

Jeff Scott Brown
  • 26,804
  • 2
  • 30
  • 47
  • This helped a lot. Thanks for hanging in with me :). Sorry for the late reply. I am trying to figure out why Stack exchange is not sending notifications. – Mike Houston Jan 27 '20 at 12:39
1

You can utilise Micronaut environments to create different environment configuration for test and production and configure respective datasource configuration in application-test.yml and use that datasource for tests

Micronaut Environments from docs

Swanand Keskar
  • 1,023
  • 1
  • 13
  • 27
  • After comments by Jeff Scott Brown, x80284 and Swanand, I realize that using H2 for test is not a good idea with Micronaut-data. I will only use one dialect (Postgres) for test and production. I will mark this answer as correct since you all made me re-think my approach. Thanks :). The Micronaut-data docs makes this clear. – Mike Houston Dec 03 '19 at 11:26
  • "After comments by Jeff Scott Brown, x80284 and Swanand, I realize that using H2 for test is not a good idea with Micronaut-data" - I am not sure what I said that made you think that, but I use H2 for test all the time. I think it is a good idea for a lot of apps. – Jeff Scott Brown Dec 04 '19 at 15:29
  • Sorry, I just tried to include everyone since all of the comments made me re-examine my approach. I have nothing against H2 for tests. What I meant was: My production servers will use Postgres. I decided to not make a copy of each repository just for testing under H2. I did not see any other way to test with H2 and use Postgres for Production. – Mike Houston Dec 04 '19 at 20:14
  • "I did not see any other way to test with H2 and use Postgres for Production. " - One way is what I described in my answer. See https://github.com/jeffbrown/mikehoustonrepository/blob/82b8af568042c762a86cef9965e52fdc61053421/src/test/java/mikehoustonrepository/TestBookRepository.java. – Jeff Scott Brown Dec 10 '19 at 17:50
1

After some more work, I found another way to solve the original problem. You can define a base interface class that just has the methods you need. Then implement concrete classes for the dialect(s) you need. This allows one type of DB for test and one for production.

interface OrderRepository extends BaseRepository, CrudRepository<Order, UUID> {
  @Join(value = "product", type = Join.Type.LEFT_FETCH)
  Optional<Order> findById(UUID uuid)
}

@JdbcRepository(dialect = Dialect.H2)
@Requires(env = ["test"])
interface OrderRepositoryH2 extends OrderRepository, CrudRepository<Order, UUID> {
}

@JdbcRepository(dialect = Dialect.POSTGRES)
@Requires(env = ["dev"])
interface OrderRepositoryPostgres extends OrderRepository, CrudRepository<Order, UUID> {
}

No methods are needed in the OrderRepositoryH2 interface. Micronaut-data uses the methods from the parent interface fine. The trick is to not use the @JdbcRepository annotation in the parent interface.

You can create any other dialects needed, but you have to make sure the @Requires annotation results in only one bean for any given mode.

I plan to use H2 for testing, with an option to use the Postgres dialect for special test runs when needed.

Sorry for any confusion on the question and comments. (I decided to mark this as the answer since it solves the original problem).

Mike Houston
  • 246
  • 2
  • 8
  • You could use 1 less interface. `interface OrderRepository extends CrudRepository` and marked with `@JdbcRepository(dialect = Dialect.H2)`, interface OrderRepositoryPostgres extends OrderRepository` and marked with `@JdbcRepository(dialect = Dialect.POSTGRES)` and `@Replaces(OrderRepository)`. See https://github.com/jeffbrown/mnpgdemo/blob/3572d2c682aea21968c5010862938d119946025f/src/main/java/mnpgdemo/CarRepository.java and https://github.com/jeffbrown/mnpgdemo/blob/3572d2c682aea21968c5010862938d119946025f/src/main/java/mnpgdemo/PostgresCarRepository.java for an example. – Jeff Scott Brown Dec 10 '19 at 15:20
  • The way you have it written, I don't think there is any reason to have `OrderRepositoryPostgres` and `OrderRepositoryH2` extend `CrudRepository` because they both extend `OrderRepository` which extends `CrudRepository`. – Jeff Scott Brown Dec 10 '19 at 15:24
  • Another thing I would change about this is instead of marking `OrderRepositoryH2` with `@Requires(env = ["test"])` I would remove `@Requires`, move the source code to `src/test/` and mark it with `@Replaces(WhateverTheProductionRepositoryIs)`. That has the benefit of not including `OrderRepositoryH2` and its associated support classes in your production jar file. – Jeff Scott Brown Dec 10 '19 at 15:32
  • Good point on the src/test. I have not really settled on the best approach. I want to be able to run all tests against Postgres, so I would need something to revert back to Postgres in test. – Mike Houston Dec 10 '19 at 19:43
  • Thanks, that looks like what I needed to do. I think the only difference is that I was using Groovy. That might the where the compiler issue comes from. Let's leave this as-is for now. I am happy with the current approach (very close to your approach). – Mike Houston Dec 10 '19 at 19:51
  • "I think the only difference is that I was using Groovy. That might the where the compiler issue comes from." - I will see if I can replicate. If the Groovy AST Transformations are broken with respect to this, we would like to fix it before releasing Micronaut Data 1.0. Thanks for the feedback. – Jeff Scott Brown Dec 10 '19 at 20:51
  • "I think the only difference is that I was using Groovy. That might the where the compiler issue comes from." - The commit at https://github.com/jeffbrown/mikehoustonrepository/commit/0f51ec61f833ff259e0afc4ceddb66fc16fa0943 replaces the Java implementation with a Groovy implementation, and that also appears to work. – Jeff Scott Brown Dec 10 '19 at 22:38