Skip to content

Commit

Permalink
Merge pull request #39302 from karesti/mock-support
Browse files Browse the repository at this point in the history
Adds RemoteCache @InjectMock support in Infinispan
  • Loading branch information
gsmet authored Mar 13, 2024
2 parents 03c013a + c98e2cc commit f82cb64
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 15 deletions.
94 changes: 92 additions & 2 deletions docs/src/main/asciidoc/infinispan-client-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,7 @@ the field, constructor or method. In the below code we utilize field and constru
If you notice the `RemoteCache` declaration has an additional annotation named `Remote`.
This is a *qualifier* annotation allowing you to specify which named cache that will be injected. This
annotation is not required and if it is not supplied, the default cache will be injected.
The RemoteCacheManager bean scope is `@ApplicationScope`.
The RemoteCache bean scope is `@Singleton`.
The RemoteCacheManager and RemoteCache bean scope is `@ApplicationScoped`.

For non default connections, combine the qualifier `@InfinispanClientName` and `@Remote`.

Expand All @@ -635,6 +634,62 @@ For non default connections, combine the qualifier `@InfinispanClientName` and `

NOTE: Other types may be supported for injection, please see other sections for more information

=== Mock Support
Quarkus supports the use of mock objects using two different approaches. You can either use CDI alternatives to
mock out a bean for all test classes, or use `QuarkusMock` to mock out beans on a per test basis.
Check the xref:getting-started-testing.adoc[Getting started with testing guide] for more information.

RemoteCacheManager and RemoteCache can be mocked.

.BookService.java
[source,java]
----
@ApplicationScoped
public class BookService {
@Inject
@Remote("books")
RemoteCache<String, Book> books; //<1>
public String getBookDescriptionById(String id) {
Book book = books.get(id);
if (book == null) {
return "default";
}
return book.getDescription();
}
}
----
<1> Use dependency injection to connect to the books cache

In the test class, the RemoteCache can be mocked.

.BookServiceTest.java
[source,java]
----
@QuarkusTest
public class BookServiceTest {
@Inject
BookService bookService;
@InjectMock // <1>
@Remote("books")
RemoteCache<String, Book> bookRemoteCache;
@Test
public void mockRemoteCache() {
Mockito.when(bookRemoteCache.get("harry_potter")).thenReturn(new Book(... "Best saga ever");//<2>
Assertions.assertThat(bookService.getBookDescriptionById("harry_potter")).isEqualTo("Best saga ever");//<3>
}
}
----
<1> Inject a mock instead of the RemoteCache bean
<2> Use Mockito to mock the call of the RemoteCache
<3> Assert the service call

=== Registering Protobuf Schemas with Infinispan Server
You need to register the generated Protobuf schemas with Infinispan Server to perform queries or convert from
`Protobuf` to other media types such as `JSON`.
Expand Down Expand Up @@ -782,8 +837,43 @@ public class Book {
You can use either the Query DSL or the Ickle Query language with the Quarkus Infinispan client
extension.

.Query.java
[source,java]
----
@Inject
@Remote("books")
RemoteCache<String, Book> booksCache; //<1>
Query<Book> query = booksCache.query("from book_sample.Book b where b.authors.name like '%" + name + "%'"); //<2>
List<Book> list = query.execute().list();
----
<1> Inject the books cache
<2> Perform a full text query on books author name

NOTE: You can read more about https://infinispan.org/docs/stable/titles/query/query.html[querying] in the Infinispan documentation.

[IMPORTANT]
====
Prior to Quarkus 3.9 and the Infinispan 15 integration, queries were executed by calling the following code:
.Query.java
[source,java]
----
QueryFactory queryFactory = Search.getQueryFactory(booksCache); <1>
Query query = queryFactory.create("from book_sample.Book");
List<Book> list = query.execute().list();
----
<1> Breaking change in 3.9
This code won't work anymore since `RemoteCache` is now an `@ApplicationScoped` proxy bean.
`Search.getQueryFactory` will raise a ClassCastException.
Remove the unecessary indirection by using the `query` method in the `RemoteCache` API as follows.
[source,java]
----
Query<Book> query = booksCache.<Book>query("from book_sample.Book");
List<Book> list = query.execute().list();
----
====

== Counters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Default;
import jakarta.inject.Singleton;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
Expand Down Expand Up @@ -591,7 +590,7 @@ static <T> SyntheticBeanBuildItem configureAndCreateSyntheticBean(String name,
static <T> SyntheticBeanBuildItem configureAndCreateSyntheticBean(RemoteCacheBean remoteCacheBean, Supplier<T> supplier) {
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem.configure(RemoteCache.class)
.types(remoteCacheBean.type)
.scope(Singleton.class) // Some Infinispan API won't work if this is not a Mock
.scope(ApplicationScoped.class)
.supplier(supplier)
.unremovable()
.setRuntimeInit();
Expand Down
9 changes: 9 additions & 0 deletions integration-tests/infinispan-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-mockito</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>

<!-- Minimal test dependencies to *-deployment artifacts for consistent build order -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.quarkus.it.infinispan.client;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;

import io.quarkus.infinispan.client.Remote;

@ApplicationScoped
public class BookService {
public static final String DEFAULT_DESCRIPTION = "Default description";

@Inject
RemoteCacheManager cacheManager;

@Inject
@Remote(CacheSetup.BOOKS_CACHE)
RemoteCache<String, Book> books;

public String getBookDescriptionById(String id) {
Book book = books.get(id);
if (book == null) {
return DEFAULT_DESCRIPTION;
}

return book.description();
}

public String getBookDescriptionById(String cacheName, String id) {
RemoteCache<String, Book> cache = cacheManager.getCache(cacheName);
if (cache == null) {
throw new IllegalArgumentException("Cache not found");
}
Book book = cache.get(id);
if (book == null) {
return DEFAULT_DESCRIPTION;
}
return book.description();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,16 @@
import jakarta.ws.rs.core.Response;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.Search;
import org.infinispan.client.hotrod.jmx.RemoteCacheClientStatisticsMXBean;
import org.infinispan.client.hotrod.logging.Log;
import org.infinispan.client.hotrod.logging.LogFactory;
import org.infinispan.commons.api.query.Query;
import org.infinispan.counter.api.CounterConfiguration;
import org.infinispan.counter.api.CounterManager;
import org.infinispan.counter.api.CounterType;
import org.infinispan.counter.api.Storage;
import org.infinispan.counter.api.StrongCounter;
import org.infinispan.counter.api.WeakCounter;
import org.infinispan.query.dsl.Query;
import org.infinispan.query.dsl.QueryFactory;

import io.quarkus.infinispan.client.InfinispanClientName;
import io.quarkus.infinispan.client.Remote;
Expand Down Expand Up @@ -86,10 +84,7 @@ public String getCachedValue(@PathParam("id") String id) {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String queryAuthorSurname(@PathParam("id") String name) {
QueryFactory queryFactory = Search.getQueryFactory(cache);
Query query = queryFactory.from(Book.class)
.having("authors.name").like("%" + name + "%")
.build();
Query<Book> query = cache.query("from book_sample.Book b where b.authors.name like '%" + name + "%'");
List<Book> list = query.execute().list();
if (list.isEmpty()) {
return "No one found for " + name;
Expand All @@ -107,8 +102,7 @@ public String queryAuthorSurname(@PathParam("id") String name) {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String ickleQueryAuthorSurname(@PathParam("id") String name) {
QueryFactory queryFactory = Search.getQueryFactory(cache);
Query query = queryFactory.create("from book_sample.Book b where b.authors.name like '%" + name + "%'");
Query<Book> query = cache.query("from book_sample.Book b where b.authors.name like '%" + name + "%'");
List<Book> list = query.execute().list();
if (list.isEmpty()) {
return "No one found for " + name;
Expand Down Expand Up @@ -255,8 +249,8 @@ public Response createItem(String value, @PathParam("id") String id) {
@Path("magazinequery/{id}")
@GET
public String magazineQuery(@PathParam("id") String name) {
QueryFactory queryFactory = Search.getQueryFactory(magazineCache);
Query query = queryFactory.create("from magazine_sample.Magazine m where m.name like '%" + name + "%'");
Query<Magazine> query = magazineCache.query(
"from magazine_sample.Magazine m where m.name like '%" + name + "%'");
List<Magazine> list = query.execute().list();
if (list.isEmpty()) {
return "No one found for " + name;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.quarkus.it.infinispan.client;

import static io.quarkus.it.infinispan.client.BookService.DEFAULT_DESCRIPTION;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.math.BigDecimal;
import java.util.Collections;

import jakarta.inject.Inject;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.junit.jupiter.api.Test;

import io.quarkus.infinispan.client.Remote;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class MockInfinispanClientTest {

@Inject
BookService bookService;

@InjectMock
RemoteCacheManager cacheManager;

@InjectMock
@Remote(CacheSetup.BOOKS_CACHE)
RemoteCache<String, Book> bookRemoteCache;

private final Book BOOK = new Book("Full Saga of Harry Potter", "Best saga ever", 1997,
Collections.emptySet(), Type.FANTASY, new BigDecimal("500.99"));

@Test
public void mockRemoteCacheManager() {
RemoteCache<String, Book> localMockCache = mock(RemoteCache.class);
when(localMockCache.get("harry_potter")).thenReturn(BOOK);
when(cacheManager.<String, Book> getCache(CacheSetup.BOOKS_CACHE)).thenReturn(localMockCache);

assertThat(bookService.getBookDescriptionById(CacheSetup.BOOKS_CACHE, "non_exist")).isEqualTo(DEFAULT_DESCRIPTION);
assertThat(bookService.getBookDescriptionById(CacheSetup.BOOKS_CACHE, "harry_potter")).isEqualTo("Best saga ever");
assertThatIllegalArgumentException().isThrownBy(
() -> bookService.getBookDescriptionById(CacheSetup.DEFAULT_CACHE, "non_exist"));
}

@Test
public void mockRemoteCache() {
when(bookRemoteCache.get("harry_potter")).thenReturn(BOOK);
assertThat(bookService.getBookDescriptionById("non_exist")).isEqualTo(DEFAULT_DESCRIPTION);
assertThat(bookService.getBookDescriptionById("harry_potter")).isEqualTo("Best saga ever");
}
}

0 comments on commit f82cb64

Please sign in to comment.