1
votes

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.

3
Are you saying that when BookRepository defines Optional<Book> find(String title) and TestBookRepository extends BookRepository that the find method is not inherited into TestBookRepository? - Jeff Scott Brown
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
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
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
I think what you are describing would require a bug in the Java compiler. - Jeff Scott Brown

3 Answers

2
votes

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.

1
votes

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

1
votes

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).