What I'm trying to do is test an authentication handler, but my problem boils down to having no Session instance in the registry.
An example test:
package whatever
import groovy.transform.CompileStatic
import ratpack.groovy.handling.GroovyChainAction
import ratpack.groovy.test.handling.GroovyRequestFixture
import ratpack.http.Status
import ratpack.session.Session
import spock.lang.Specification
class SessionChainTest extends Specification {
GroovyChainAction sessionChain = new GroovyChainAction() {
#Override
#CompileStatic
void execute() throws Exception {
get('foo') {
Session s = get Session
// Stuff using session here
}
}
}
def "should get session"() {
given:
def result = GroovyRequestFixture.handle(sessionChain) {
uri 'foo'
method 'GET'
}
expect:
result.status == Status.OK
// If the server threw, rethrow that
Throwable t = result.exception(Throwable)
if (t) throw t // <<< Throws NotInRegistryException because no Session in registry
}
}
(The extra rethrow is in there to allow us to see the exception thrown within the ratpack test, because by default it is caught and stashed in the result.)
I know that in principle I could create a Session instance and add it to the registry with a registry { add <Session instance> } block, but I've delved into the Ratpack code, and creating a Session object requires getting a lot of disparate other components and passing them to SessionModule#sessionAdaptor (or the DefaultSession constructor). I can't find any examples of that being done, it appears this call is handled by Guice dependency-injection magic I can't unpick.
The usual way to do it in an application is to use a bind { module SessionModule } block but this isn't accessible from the context of RequestFixture#execute.
As sessions are bread and butter for any web application, my hunch is that this may be an easily solved problem, I just haven't found the right way to do it?
You can access Registry through GroovyRequestFixture.handle(handler, closure) method call and you can e.g. register mocked Session object:
GroovyRequestFixture.handle(sessionChain) {
uri 'foo'
method 'GET'
registry { r ->
r.add(Session, session)
}
}
Take a look at following example:
import groovy.transform.CompileStatic
import ratpack.exec.Promise
import ratpack.groovy.handling.GroovyChainAction
import ratpack.groovy.test.handling.GroovyRequestFixture
import ratpack.http.Status
import ratpack.jackson.internal.DefaultJsonRender
import ratpack.session.Session
import spock.lang.Specification
import static ratpack.jackson.Jackson.json
class SessionChainTest extends Specification {
Session session = Mock(Session) {
get('test') >> Promise.value(Optional.of('Lorem ipsum'))
}
GroovyChainAction sessionChain = new GroovyChainAction() {
#Override
#CompileStatic
void execute() throws Exception {
get('foo') {
Session s = get Session
s.get('test').map { Optional<String> o ->
o.orElse(null)
}.flatMap { value ->
Promise.value(value)
}.then {
render(json([message: it]))
}
}
}
}
def "should get session"() {
given:
def result = GroovyRequestFixture.handle(sessionChain) {
uri 'foo'
method 'GET'
registry { r ->
r.add(Session, session)
}
}
expect:
result.status == Status.OK
and:
result.rendered(DefaultJsonRender).object == [message: 'Lorem ipsum']
}
}
In this test I mock Session object for key test to store Lorem ipsum text. When running this test, both assertions pass.
Alternative approach: registering Guice.registry()
If you don't want to use mocked Session object you can try replacing default Ratpack's Registry with a Guice registry object. Firstly, initialize a function that creates Guice registry and add SessionModule via bindings:
static Function<Registry, Registry> guiceRegistry = Guice.registry { bindings ->
bindings.module(new SessionModule())
}
Next inside execute() method of GroovyChainAction you can replace the default registry by calling:
register(guiceRegistry.apply(registry))
No mocks anymore, but in this case you can't access Session object outside request scope, so you wont be able to add anything to the session in preparation stage of your test. Below you can find full example:
import groovy.transform.CompileStatic
import ratpack.exec.Promise
import ratpack.func.Function
import ratpack.groovy.handling.GroovyChainAction
import ratpack.groovy.test.handling.GroovyRequestFixture
import ratpack.guice.Guice
import ratpack.http.Status
import ratpack.jackson.internal.DefaultJsonRender
import ratpack.registry.Registry
import ratpack.session.Session
import ratpack.session.SessionModule
import spock.lang.Specification
import static ratpack.jackson.Jackson.json
class SessionChainTest extends Specification {
static Function<Registry, Registry> guiceRegistry = Guice.registry { bindings ->
bindings.module(new SessionModule())
}
GroovyChainAction sessionChain = new GroovyChainAction() {
#Override
#CompileStatic
void execute() throws Exception {
register(guiceRegistry.apply(registry))
get('foo') {
Session s = get Session
s.get('test').map { Optional<String> o ->
o.orElse(null)
}.flatMap { value ->
Promise.value(value)
}.then {
render(json([message: it]))
}
}
}
}
def "should get session"() {
given:
def result = GroovyRequestFixture.handle(sessionChain) {
uri 'foo'
method 'GET'
}
expect:
result.status == Status.OK
and:
result.rendered(DefaultJsonRender).object == [message: null]
}
}
Hope it helps.
Related
Resilience4j version: 1.7.0
Java version: 1.8
I have challenge in implementing TimeLimiter feature of Resilience4j. I am able to get the Circuit Breaker (CB) work.
We have 2 services Lets say serviceA and serviceB. We have used Command design pattern which encapsulates logic to communicate with ServiceB. RabbitMQ is used to establish inter microservice communication. We had implemented Hystrix CB by making all our Command classes extend HystrixCommand. When we decided to move to Resilience4j main challenge was to retain the existing design pattern than configuring Resilence4J CB.
We have Synchronous communication at present between ServiceA and ServiceB. Though we use RabbitMQ to communicate which is Async communication, with the help of Spring wrapper method RabbitTemplate.convertSendAndReceive() we are able to achieve Sync mode of communication with RabbitMQ.
When I removed HystrixCommand reference which was the Base class for all my Command classes, naturally there was a need to implement a custom Base Command class which will be implemented using Resilience4J Decorators.
I managed introduce a Resilience4JCommand abstract class which will implement a execute() and execute run() from all my command classes. Also defined a abstract run() which all my existing Command classes will override and implement business logic.
I understood from many of the discussion that our method which needs to implement CB pattern needs to return of type CompletableFuture and also understood from many places that fallback method also must have same return type. My Base Command Class Resilience4JCommand looks something like below
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import com.ge.hc.XYZ.exception.ResourceNotFoundException;
import io.github.resilience4j.bulkhead.annotation.Bulkhead;
import io.github.resilience4j.bulkhead.annotation.Bulkhead.Type;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;
#Component
public abstract class Resilience4JCommand<R> {
/** The class logger. */
protected static final Logger LOGGER = LoggerFactory.getLogger(Resilience4JCommand.class);
public R execute() {
R result = null;
try {
result = executeWithCircuitBreaker().get();
} catch (Exception e) {
System.out.println("Inside Catch block of executeAsync ...........**************\n\n ");
e.printStackTrace();
throw new RuntimeException(e);
}
return result;
}
#Bulkhead(name = "XYZStreamingServer3", fallbackMethod = "getFallback", type = Bulkhead.Type.THREADPOOL)
#TimeLimiter(name = "XYZStreamingServer2", fallbackMethod = "getFallback")
#CircuitBreaker(name = "XYZStreamingServer1", fallbackMethod = "getFallback")
public CompletableFuture<R> executeWithCircuitBreaker() {
return CompletableFuture.supplyAsync(new Supplier<R>() {
#Override
public R get() {
return run();
}
});
}
protected abstract R run();
public CompletableFuture<R> getFallback(Throwable e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
if (e != null) {
e.printStackTrace(pw);
}
String reason = sw.toString();
LOGGER.error("Calling XYZ-hystrix fallback method for command: {}; fallback reason: {}",
this.getClass().getSimpleName(), (reason.isEmpty() ? "unknown" : reason));
throw new ResourceNotFoundException("Circuit Breaker ");
}
}
But nothing works with above setup. I am able to achieve CB alone work without the need of writing new method executeWithCircuitBreaker() which returns CompletableFuture. I can make CB work just with below execute()
Bulkhead AND TimeLimiter do not work with return type other than CompletableFuture
#CircuitBreaker(name = SCHEME_NAME, fallbackMethod = "getFallback")
public R execute() {
return run();
}
I have spent more than a week in setting up this .. Helpful if someone can point me what I am missing 😢
My application.properties looks something like belwo
management.health.circuitbreakers.enabled=true
management.endpoints.web.exposure.include=health
management.endpoint.health.show-details=always
resilience4j.circuitbreaker.instances.XYZStreamingServer1.registerHealthIndicator=true
resilience4j.circuitbreaker.instances.XYZStreamingServer1.eventConsumerBufferSize=10
resilience4j.circuitbreaker.instances.XYZStreamingServer1.failureRateThreshold=50
resilience4j.circuitbreaker.instances.XYZStreamingServer1.minimumNumberOfCalls=5
resilience4j.circuitbreaker.instances.XYZStreamingServer1.automaticTransitionFromOpenToHalfOpenEnabled=true
resilience4j.circuitbreaker.instances.XYZStreamingServer1.waitDurationInOpenState=5s
resilience4j.circuitbreaker.instances.XYZStreamingServer1.permittedNumberOfCallsInHalfOpenState=3
resilience4j.circuitbreaker.instances.XYZStreamingServer1.slidingWindowSize=10
resilience4j.circuitbreaker.instances.XYZStreamingServer1.slidingWindowType=COUNT_BASED
resilience4j.timelimiter.instances.XYZStreamingServer2.timeoutDuration=5s
resilience4j.timelimiter.instances.XYZStreamingServer2.cancelRunningFuture=true
resilience4j.thread-pool-bulkhead.instances.XYZStreamingServer3.maxThreadPoolSize=10
resilience4j.thread-pool-bulkhead.instances.XYZStreamingServer3.coreThreadPoolSize=5
resilience4j.thread-pool-bulkhead.instances.XYZStreamingServer3.queueCapacity=5
I referenced with the blog post Contextual Logging with Reactor Context and MDC but I don't know how to access reactor context in WebFilter.
#Component
public class RequestIdFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
List<String> myHeader = exchange.getRequest().getHeaders().get("X-My-Header");
if (myHeader != null && !myHeader.isEmpty()) {
MDC.put("myHeader", myHeader.get(0));
}
return chain.filter(exchange);
}
}
Here's one solution based on the latest approach, as of May 2021, taken from the official documentation:
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Signal;
import reactor.util.context.Context;
#Slf4j
#Configuration
public class RequestIdFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String requestId = getRequestId(request.getHeaders());
return chain
.filter(exchange)
.doOnEach(logOnEach(r -> log.info("{} {}", request.getMethod(), request.getURI())))
.contextWrite(Context.of("CONTEXT_KEY", requestId));
}
private String getRequestId(HttpHeaders headers) {
List<String> requestIdHeaders = headers.get("X-Request-ID");
return requestIdHeaders == null || requestIdHeaders.isEmpty()
? UUID.randomUUID().toString()
: requestIdHeaders.get(0);
}
public static <T> Consumer<Signal<T>> logOnEach(Consumer<T> logStatement) {
return signal -> {
String contextValue = signal.getContextView().get("CONTEXT_KEY");
try (MDC.MDCCloseable cMdc = MDC.putCloseable("MDC_KEY", contextValue)) {
logStatement.accept(signal.get());
}
};
}
public static <T> Consumer<Signal<T>> logOnNext(Consumer<T> logStatement) {
return signal -> {
if (!signal.isOnNext()) return;
String contextValue = signal.getContextView().get("CONTEXT_KEY");
try (MDC.MDCCloseable cMdc = MDC.putCloseable("MDC_KEY", contextValue)) {
logStatement.accept(signal.get());
}
};
}
}
Given you have the following line in your application.properties:
logging.pattern.level=[%X{MDC_KEY}] %5p
then every time an endpoint is called your server logs will contain a log like this:
2021-05-06 17:07:41.852 [60b38305-7005-4a05-bac7-ab2636e74d94] INFO 20158 --- [or-http-epoll-6] my.package.RequestIdFilter : GET http://localhost:12345/my-endpoint/444444/
Every time you want to log manually something within a reactive context you will have add the following to your reactive chain:
.doOnEach(logOnNext(r -> log.info("Something")))
If you want the X-Request-ID to be propagated to other services for distributed tracing, you need to read it from the reactive context (not from MDC) and wrap your WebClient code with the following:
Mono.deferContextual(
ctx -> {
RequestHeadersSpec<?> request = webClient.get().uri(uri);
request = request.header("X-Request-ID", ctx.get("CONTEXT_KEY"));
// The rest of your request logic...
});
You can do something similar to below, You can set the context with any class you like, for this example I just used headers - but a custom class will do just fine.
If you set it here, then any logging with handlers etc will also have access to the context.
The logWithContext below, sets the MDC and clears it after. Obviously this can be replaced with anything you like.
public class RequestIdFilter implements WebFilter {
private Logger LOG = LoggerFactory.getLogger(RequestIdFilter.class);
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
HttpHeaders headers = exchange.getRequest().getHeaders();
return chain.filter(exchange)
.doAfterSuccessOrError((r, t) -> logWithContext(headers, httpHeaders -> LOG.info("Some message with MDC set")))
.subscriberContext(Context.of(HttpHeaders.class, headers));
}
static void logWithContext(HttpHeaders headers, Consumer<HttpHeaders> logAction) {
try {
headers.forEach((name, values) -> MDC.put(name, values.get(0)));
logAction.accept(headers);
} finally {
headers.keySet().forEach(MDC::remove);
}
}
}
As of Spring Boot 2.2 there is Schedulers.onScheduleHook that enables you to handle MDC:
Schedulers.onScheduleHook("mdc", runnable -> {
Map<String, String> map = MDC.getCopyOfContextMap();
return () -> {
if (map != null) {
MDC.setContextMap(map);
}
try {
runnable.run();
} finally {
MDC.clear();
}
};
});
Alternatively, Hooks.onEachOperator can be used to pass around the MDC values via subscriber context.
http://ttddyy.github.io/mdc-with-webclient-in-webmvc/
This is not full MDC solution, e.g. in my case I cannot cleanup MDC values in R2DBC threads.
UPDATE: this article really solves my MDC problem: https://www.novatec-gmbh.de/en/blog/how-can-the-mdc-context-be-used-in-the-reactive-spring-applications/
It provides correct way of updating MDC based on subscriber context.
Combine it with SecurityContext::class.java key populated by AuthenticationWebFilter and you will be able to put user login to your logs.
My solution based on Reactor 3 Reference Guide approach but using doOnSuccess instead of doOnEach.
The main idea is to use Context for MDC propagation in the next way:
Fill a downstream Context (which will be used by derived threads) with the MDC state from an upstream flow (can be done by .contextWrite(context -> Context.of(MDC.getCopyOfContextMap())))
Access the downstream Context in derived threads and fill MDC in derived thread with values from the downstream Context (the main challenge)
Clear the MDC in the downstream Context (can be done by .doFinally(signalType -> MDC.clear()))
The main problem is to access a downstream Context in derived threads. And you can implement step 2 with the most convenient for you approach). But here is my solution:
webclient.post()
.bodyValue(someRequestData)
.retrieve()
.bodyToMono(String.class)
// By this action we wrap our response with a new Mono and also
// in parallel fill MDC with values from a downstream Context because
// we have an access to it
.flatMap(wrapWithFilledMDC())
.doOnSuccess(response -> someActionWhichRequiresFilledMdc(response)))
// Fill a downstream context with the current MDC state
.contextWrite(context -> Context.of(MDC.getCopyOfContextMap()))
// Allows us to clear MDC from derived threads
.doFinally(signalType -> MDC.clear())
.block();
// Function which implements second step from the above main idea
public static <T> Function<T, Mono<T>> wrapWithFilledMDC() {
// Using deferContextual we have an access to downstream Context, so
// we can just fill MDC in derived threads with
// values from the downstream Context
return item -> Mono.deferContextual(contextView -> {
// Function for filling MDC with Context values
// (you can apply your action)
fillMdcWithContextView(contextView);
return Mono.just(item);
});
}
public static void fillMdcWithContextValues(ContextView contextView) {
contextView.forEach(
(key, value) -> {
if (key instanceof String keyStr && value instanceof String valueStr) {
MDC.put(keyStr, valueStr);
}
});
}
This approach is also can be applied to doOnError and onErrorResume methods since the main idea is the same.
Used versions:
spring-boot: 2.7.3
spring-webflux: 5.3.22 (from spring-boot)
reactor-core: 3.4.22 (from spring-webflux)
reactor-netty: 1.0.22 (from spring-webflux)
I achieved this with :-
package com.nks.app.filter;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
/**
* #author nks
*/
#Component
#Slf4j
public class SessionIDFilter implements WebFilter {
private static final String APP_SESSION_ID = "app-session-id";
/**
* Process the Web request and (optionally) delegate to the next
* {#code WebFilter} through the given {#link WebFilterChain}.
*
* #param serverWebExchange the current server exchange
* #param webFilterChain provides a way to delegate to the next filter
* #return {#code Mono<Void>} to indicate when request processing is complete
*/
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
serverWebExchange.getResponse()
.getHeaders().add(APP_SESSION_ID, serverWebExchange.getRequest().getHeaders().getFirst(APP_SESSION_ID));
MDC.put(APP_SESSION_ID, serverWebExchange.getRequest().getHeaders().getFirst(APP_SESSION_ID));
log.info("[{}] : Inside filter of SessionIDFilter, ADDED app-session-id in MDC Logs", MDC.get(APP_SESSION_ID));
return webFilterChain.filter(serverWebExchange);
}
}
and, values associated with app-session-id for the thread can be logged.
I have a simple Verticle that reads configuration from a properties file and loads in into vertx config. I have written a unit test to test the deployment of this verticle and possible cause of test failure is non availability of the properties file at the location.
When I run the test, unit test passes irrespective of whether I change the properties file name or path and the handler says the verticle was deployed successfully.
Am I doing something wrong here? Below is my code
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.config.ConfigStoreOptions;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.rxjava.config.ConfigRetriever;
import io.vertx.rxjava.core.AbstractVerticle;
/**
* This is the main launcher verticle, the following operations will be executed in start() method of this verticle:
* 1. Read configurations from application.properties file
* 2. Deploy all other verticles in the application
*/
public class LauncherVerticle extends AbstractVerticle {
#Override
public void start() throws Exception {
//set up configuration from the properties file
ConfigStoreOptions fileStore = new ConfigStoreOptions()
.setType("file")
.setFormat("properties")
.setConfig(new JsonObject().put("path", System.getProperty("vertex.config.path"));
//create config retriever options add properties to filestore
ConfigRetrieverOptions options = new ConfigRetrieverOptions().addStore(fileStore);
ConfigRetriever configRetriever = ConfigRetriever.create(vertx, options);
DeploymentOptions deploymentOptions = new DeploymentOptions();
//Deploy verticles after the config has been loaded
//The configurations are loaded into JsonConfig object
//This JsonConfig object can be accessed in other verticles using the config() method.
configRetriever.rxGetConfig().subscribe(s -> {
//pass on the JsonConfig object to other verticles through deployment options
deploymentOptions.setConfig(s);
vertx.deployVerticle(AnotherVerticle.class.getName(), deploymentOptions);
}, e -> {
log.error("Failed to start application : " + e.getMessage(), e);
try {
stop();
} catch (Exception e1) {
log.error("Unable to stop vertx, terminate the process manually : "+e1.getMessage(), e1);
}
});
}
}
This is my unit test
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import io.vertx.rxjava.core.Vertx;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import rx.Single;
#RunWith(VertxUnitRunner.class)
public class LoadConfigurationTest {
/**
* Config should be loaded successfully
*
* #param context
*/
#Test
public void loadConfigTest(TestContext context) {
/*
* Set the system property "vertx.config.path" with value "application.properties"
* This system property will be used in the Launcher verticle to read the config file
*/
System.setProperty("vertx.config.path", "/opt/vertx/config/application.properties");
//create vertx instance
Vertx vertx = Vertx.vertx();
Single<String> single = vertx.rxDeployVerticle(LauncherVerticle.class.getName());
single.subscribe(s -> {
vertx.rxUndeploy(s);
}, e -> {
Assert.fail(e.getMessage());
});
}
/**
* Test for negative use case - file not available in the specified location
*
* #param context
*/
#Test
public void loadConfigFailTest(TestContext context) {
//set path = non existing path
System.setProperty("vertx.config.path", "/non/existing/path/application.properties");
//create vertx instance
Vertx vertx = Vertx.vertx();
Single single = vertx.rxDeployVerticle(LauncherVerticle.class.getName());
single.subscribe(s -> {
//not executing this statement
Assert.fail("Was expecting error but Verticle deployed successfully");
}, e -> {
//not executing this statement either
System.out.println("pass");
});
}
}
Can you try the below code inside your LauncherVerticle the changes only include using AbstractVerticles start with Future which is a neat way for handling the config loading and everything around the same during your starup.
public class LauncherVerticle extends AbstractVerticle {
#Override
public void start(Future<Void> startFuture) throws Exception {
ConfigStoreOptions fileStore = new ConfigStoreOptions()
.setType("file")
.setFormat("properties")
.setConfig(new JsonObject().put("path", System.getProperty("vertex.config.path")));
ConfigRetrieverOptions options = new ConfigRetrieverOptions().addStore(fileStore);
ConfigRetriever configRetriever = ConfigRetriever.create(vertx, options);
DeploymentOptions deploymentOptions = new DeploymentOptions();
configRetriever.rxGetConfig().subscribe(s -> {
deploymentOptions.setConfig(s);
vertx.deployVerticle(AnotherVerticle.class.getName(),
deploymentOptions,
result -> startFuture.complete()
);
},
startFuture::fail
);
}
}
startFuture there, would help you to control the state of your verticle loading.
Also remember that #Constantine way for handing the test is best way, use of Async to prevent your tests passing without actually asserting anything.
Seems like there is nothing wrong with your verticle. However, there is something in tests - the asynchronous nature of verticle deployment is not taken into account. These test methods finish immediately instead of waiting for verticle deployment, and JUnit test that does not result in AssertionError is a passed test. You have to signal completion explicitly using Async.
Please see an example for your negative scenario below:
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.RunTestOnContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import io.vertx.rxjava.core.Vertx;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
#RunWith(VertxUnitRunner.class)
public class LoadConfigurationTest {
#Rule
public RunTestOnContext runTestOnContextRule = new RunTestOnContext();
#Test
public void testConfigLoading_shouldFail_whenConfigDoesNotExist(TestContext context) {
// create an Async instance that controls the completion of the test
Async async = context.async();
// set non existing path
System.setProperty("vertx.config.path", "/non/existing/path/application.properties");
// take vertx instance and wrap it with rx-ified version
Vertx vertx = Vertx.newInstance(runTestOnContextRule.vertx());
vertx.rxDeployVerticle(LauncherVerticle.class.getName()).subscribe(s -> {
context.fail("Was expecting error but Verticle deployed successfully"); // failure
}, e -> {
async.complete(); // success
});
}
}
Also please note that you can take a Vertx instance from RunTestOnContext rule (as in the snippet above).
I'm trying to unit test presenter in my Android app. Method I'm trying to test looks like this:
#Override
public boolean loadNextPage() {
if (!mIsLoading) {
mIsLoading = true;
if (mViewReference.get() != null) {
mViewReference.get().showProgress();
}
mService.search(mSearchQuery, ++mCurrentPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(itemsPage -> {
mIsLoading = false;
mTotalPages = itemsPage.getPagination().getTotalPages();
if (mViewReference.get() != null) {
mViewReference.get().showMovies(itemsPage.getItems());
}
},
error -> {
mIsLoading = false;
Log.d(LOG_TAG, error.toString());
});
}
return mTotalPages == 0 || mCurrentPage < mTotalPages;
}
mService is Retrofit interface and mService.search() method returns RxJava's Observable<SearchResults>. My unit test code looks like this:
package mobi.zona.presenters;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import com.example.api.Service;
import com.example.model.Movie;
import com.example.model.SearchResults;
import com.example.views.MoviesListView;
import rx.Observable;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
#RunWith(MockitoJUnitRunner.class)
public class SearchPresenterTest {
#Mock
Service mService;
#Mock
MoviesListView mMoviesListView;
#Test
public void testLoadNextPage() throws Exception {
String searchQuery = "the hunger games";
SearchResults searchResults = new SearchResults();
List<Movie> movies = new ArrayList<>();
searchResults.setItems(movies);
when(mService.search(searchQuery, 1)).thenReturn(Observable.just(new SearchResults()));
MoviesListPresenter presenter = new SearchPresenter(mZonaService, mMoviesListView, searchQuery);
presenter.loadNextPage();
verify(mService, times(1)).search(searchQuery, 1);
verify(mMoviesListView, times(1)).showProgress();
verify(mMoviesListView, times(1)).showMovies(movies);
}
}
The problem is the third verify(mMoviesListView, times(1)).showMovies(movies); line - it allways fails. Whem I'm trying to debug this test I see that control flow never goes into .subscribe(itemPage - {.... I think that it's something related to the fact that I'm subscribing on Schedulers.io() thread, but have no idea on how to fix this. Any ideas?
EDIT 1:
Changed the presenter to take Scheduler's as constructor parameters. Changed test to look like this:
#Test
public void testLoadNextPage() throws Exception {
String searchQuery = "the hunger games";
SearchResults searchResults = new SearchResults();
List<Movie> movies = new ArrayList<>();
searchResults.setItems(movies);
when(mZonaService.search(searchQuery, 1)).thenReturn(Observable.just(new SearchResults()));
MoviesListPresenter presenter = new SearchPresenter(mZonaService, mMoviesListView, searchQuery,
Schedulers.test(), Schedulers.test());
presenter.loadNextPage();
verify(mZonaService, times(1)).search(searchQuery, 1);
verify(mMoviesListView, times(1)).showProgress();
verify(mMoviesListView, times(1)).showMovies(movies);
}
Still getting this test failure message:
Wanted but not invoked:
mMoviesListView.showMovies([]);
-> at com.example.presenters.SearchPresenterTest.testLoadNextPage(SearchPresenterTest.java:46)
However, there were other interactions with this mock:
mMoviesListView.showProgress();
-> at com.example.presenters.SearchPresenter.loadNextPage(SearchPresenter.java:41)
In my apps interactors/use-cases/model (mService in your case) is responsible for specifying Scheduler for the operation (since it knows better what kind of operation it does).
So, move your subscribeOn to mService. After that your mock will work fine.
Going deeper, if now you'll want to test mService I would recommend you to make it "dependent" on Scheduler. In other words - add Sheduler as a constructor parameter.
public class MyService {
private final Scheduler taskScheduler;
public MyService(Scheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
// ...
public Observable<Something> query() {
return someObservable.subscribeOn(taskScheduler);
}
}
Then, in tests you can use Schedulers.immediate() and for actual app Schedulers.io() (or whatever you like, really).
I create a DWR ajaxfilter, but not sure how to config it with Spring. DWR version is 2.0.1, and i hope config it as a globel filter, so when session is expired, i can catch this exception in client side.
package com.gbtags;
import org.directwebremoting.AjaxFilter;
import org.directwebremoting.AjaxFilterChain;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.extend.LoginRequiredException;
import java.lang.reflect.Method;
public class DWRSessionFilter implements AjaxFilter {
public Object doFilter(Object obj, Method method, Object[] params, AjaxFilterChain chain) throws Exception {
//Check if session has timedout/invalidated
if( WebContextFactory.get().getSession( false ) == null ) {
System.out.println("session expired");
//Throw an exception
throw new LoginRequiredException( "This operation requires login." );
}
return chain.doFilter(obj, method, params);
}
}
add filter to dwr.xml as below: