I use SpringBoot and Java to write e2e tests for my API.
Along the flow, I am doing an HTTP call to a storage API (S3), and I am mocking it using MockServer.
This is the HttpClient and how I am creating my post request:
public class HttpClient {
private final String baseUrl;
public <T> Mono<T> post(final String path, String body, Class<T> responseType) {
return WebClient.builder()
.baseUrl(baseUrl) // localhost:1082
.build()
.post()
.uri(path)
.bodyValue(body)
.accept(MediaType.APPLICATION_JSON)
...
This is how I am configuring my mock server:
public class CommonMockServerHelpers {
private static MockServerClient mockServerClientStorage = new MockServerClient("localhost", 1082).reset();
public static MockServerClient getClientStorage() {
return mockServerClientStorage;
}
public static void verify(String path, String exceptedRequestBody, int times) {
Awaitility.await()
.atMost(Duration.ofSeconds(60))
.untilAsserted(() ->
verify(getClientStorage(), path, exceptedRequestBody, times)
);
}
public static void verify(MockServerClient client, String path, String exceptedRequestBody, int times) {
client.verify(buildPostRequest()
.withBody(subString(exceptedRequestBody))
.withPath(path), VerificationTimes.exactly(times));
}
In my tests, I am making API HTTP calls using RestTemplate. In one test this verification should pass:
CommonMockServerHelpers.verify("/save-file", "FAILED", 0);
while on the other it should not.
When running the test they collide and make each other fail.
Is there a way to create some uniqueness to each test so I'll be able to verify the MockServer calls of a test without interfering with the other tests?
you should write expectations in each test case like this:
mockServerClientStorage.reset()
.when(request()
.withMethod("GET")
.withPath("/save-file"))
.respond(response()
.withStatusCode(200)
.withBody("Nice!"));
mockServerClientStorage.reset()
.when(request()
.withMethod("GET")
.withPath("/save-file"))
.respond(response()
.withStatusCode(500)
.withBody("I failed!"));
It's important to do the reset() first, because those expectation are saved by the mockServer and will result in flaky tests otherwise. You could do the reset() in a #beforeEach-method if you like.
Related
I'm trying to unit test my GatewayFilter, however I'm having troubles running even simple test.
This is small example of what is failing right now
#ExtendWith(MockitoExtension.class)
public class SomeFilterTest {
private final GatewayFilter gatewayFilter = (exchange, chain) ->
Mono.just("Hello")
.flatMap(this::doSomething)
.switchIfEmpty(Mono.defer(() -> chain.filter(exchange)));
private Mono<Void> doSomething(String value) {
System.out.println(value);
return Mono.empty();
}
#Test
void test1() {
var exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
var chain = mock(GatewayFilterChain.class);
gatewayFilter.filter(exchange, chain).block();
}
}
Unfortunatelly, it is failing because of
The Mono returned by the supplier is null
java.lang.NullPointerException: The Mono returned by the supplier is
null at java.base/java.util.Objects.requireNonNull(Objects.java:246)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44) at
reactor.core.publisher.Mono.subscribe(Mono.java:4361)
And to be honest, I have no idea why is that happening?
You have not stubbed out the filter method call on your mock object, GatewayFilterChain. As a result, the supplier () -> chain.filter(exchange) returns null. You are not allowed to create a Mono with a value of null, hence the exception.
As a result your test should look something like
#Test
public void test1() {
var exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
var chain = mock(WebFilterChain.class);
// stubbing behaviour on our mock object
given(chain.filter(exchange)).willReturn(Mono.empty());
gatewayFilter.filter(exchange, chain).block();
}
Additionally, I would suggest using StepVerifier instead of using block() in unit tests. This is provided by reactor-test and is purpose built for unit testing reactive code
#Test
public void test1() {
var exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
var chain = mock(WebFilterChain.class);
given(chain.filter(exchange)).willReturn(Mono.empty());
StepVerifier.create(gatewayFilter.filter(exchange, chain))
.verifyComplete();
}
Here is a very useful Step Verifier Tutorial to help you get started
I am try to create a unit test using Junit and Mockito. However I am getting an error saying
class java.util.LinkedList cannot be cast to class org.reactivestreams.Publisher
When the it clearly returns a list of strings.
test
#Test
public void fullLoad() {
when((Publisher<?>) this.mockedProductComponent.getErpNumbers("US", "es")).thenReturn(
just(new ArrayList<>())
);
}
method being tested
public boolean fullLoad(String country, String language) {
List<String> erpNumbers = productComponent.getErpNumbers(country, language);
log.info("Retrieved following ERP numbers: {}", erpNumbers);
Lists.partition(erpNumbers, batchSize)
.forEach(handleBatch(country, language));
return true;
}
method trying to mock
public List<String> getOnlineErpNumbers(String country, String lang) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.scheme(scheme)
.host(host)
.path(path)
.port(port)
.pathSegment(country)
.pathSegment(lang)
.pathSegment("erp")
.build())
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<String>>(){})
.timeout(Duration.ofMillis(3000))
.onErrorReturn(Collections.emptyList())
.block();
}
I know it must be because I am doing this the reactive way but I have not been able to find documentation on how to test such a process.
You confused your imports.
You are using Mono.when from Reactor which accepts a publisher:
public static Mono<Void> when(Publisher<?>... sources)
instead of Mockito.when
public static <T> OngoingStubbing<T> when(T methodCall)
I've been looking on internet but haven't found the solution if any (new on UnitTest and Mockito)
It's possible to test a method that return a call of a service and manipulate it's result before to return it? Example;
public Observable<Reports> getUserReports(Integer userId) {
return serviceClient
.getReports(userId)
.flatMap(serviceReports -> {
System.out.println("Testing inside flatMap"); <- never reach this line therefore duno if methods down here are invoked and work perfectly
final Observable<List<Report>> reports = getPendingReports(userId, serviceReports);
//More methods that modify/update information
return zip(observable1, observable2, observable3
(report1, report2, report3) -> {
updateReports(otherArguments, report1, report2, report3);
return serviceReports;
});
});
}
So far I've tried;
#Test
public void myTest(){
when(serviceClient
.getReports(anyInt()))
.thenReturn(Observable.just(reports));
Observable<Reports> result = mocketClass.getUserReports(userId)
}
Tryed with Spy and Mock but no luck so far. Any hint or help would be great.
To mock getReports() behavior you need to mock the serviceClient firstly and pass it into your service class.
Just as example:
#Test
public void myTest(){
// Given
final ServiceClient mockedServiceClient = Mockito.mock(ServiceClient.class);
when(mockedServiceClient
.getReports(anyInt()))
.thenReturn(Observable.just(reports));
// and create an instance of your class under testing with injected mocked service client.
final MyUserService userService = new MyUserService();
userService.setServiceClient(mockedServiceClient);
// When run a method under test
Observable<Reports> actualResult = userService.getUserReports(userId)
// Then
// actualResult to be verified.
}
I'm trying to set up JunitTest using camel, activeMq and an Alfresco API
The route I want to test is :
from(Constantes.Direct.DIRECT_GET_AUTHENTIFICATION_TICKET)
.setBody().simple("{"
+ "\"userId\": \"userId\","
+"\"password\": \"password\""
+"}")
.setHeader(Exchange.HTTP_METHOD,constant(Constantes.Headers.HTTP_POST))
.setHeader(Exchange.HTTP_URI,simple(Constantes.Urls.OBTENIR_TICKET))
.to(Constantes.Urls.DUMMYHOST).convertBodyTo(String.class)
.unmarshal().json(JsonLibrary.Jackson, TicketAlfresco.class).process(new Dumper())
.process(new TokenBase64Proc())
.setHeader(Constantes.Headers.SENDER, constant(Constantes.Headers.ALFRESCO))
.setHeader(Constantes.Headers.API_ACTION, constant(SET_ALFRESCO_TOKEN))
.setHeader(Constantes.Headers.HEADER_AUTHORIZATION, simple("${body}"))
.inOut(Constantes.ActiveMq.ACTIVEMQ_IN)
.end();
The first "to" send a request to the Alfresco API and give back a new token.
The last inOut send the token to an activeMQ.
The problem is that when I want to test my route, when the test arrive to inOut inside the activeMq, the test fail because it didn't get any answer.
Do I need to install and embeded broker activeMQ or do I need to Mock the ActiveMQ ? And how can I do that?
For the moment to make it run I use :
mockEndpointsAndSkip("activemq:IN")
But I'm not sure that is the good solution.
Here is the test I have for the moment:
#RunWith(SpringRunner.class)
#EnableAutoConfiguration
#ComponentScan(basePackages = {"fr.gif.wsp.web.service.alfresco*"})
public class RouteGetAuthentificationTicketTest extends CamelTestSupport{
#Autowired private RouteGetAuthentificationTicket routeGetAuthentificationTicket;
//Route to test
private final static String FOURNISSEUR_GET_AUTHENTIFICATION_TICKET = Constantes.Direct.DIRECT_GET_AUTHENTIFICATION_TICKET;
private final static String MOCK_FOURNISSEUR_GET_AUTHENTIFICATION_TICKET = "mock:" + FOURNISSEUR_GET_AUTHENTIFICATION_TICKET;
// Mock result
private final static String MOCK_RESULT = "mock:result";
//Data
private final static String BODY = "Content of the body";
#Override
protected RoutesBuilder createRouteBuilder() {
return routeGetAuthentificationTicket;
}
#Before
public void setContextRoute() throws Exception {
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
mockEndpointsAndSkip("activemq:IN");
weaveAddLast().to(MOCK_RESULT);
}
});
}
#Test
public void getAuthentificationTicket() throws InterruptedException {
final MockEndpoint resultEndpoint = context.getEndpoint(MOCK_FOURNISSEUR_GET_AUTHENTIFICATION_TICKET, MockEndpoint.class);
context.createProducerTemplate().sendBody(FOURNISSEUR_GET_AUTHENTIFICATION_TICKET, BODY);
resultEndpoint.assertIsSatisfied();
final Object result = context.createProducerTemplate().requestBody(FOURNISSEUR_GET_AUTHENTIFICATION_TICKET, BODY);
assertNotNull(result);
}}
Thanks for your time
I want to test my services in spring which should send emails.
I try to use org.subethamail:subethasmtp.
To acieve my goal I created service MySender where I send email:
#Autowired
private MailSender mailSender;
//...
SimpleMailMessage message = new SimpleMailMessage();
message.setTo("example#example.com");
message.setSubject("Subject");
message.setText("Text");
mailSender.send(message);
// ...
To test this piece of code I created test application.properties (in test scope):
spring.mail.host=127.0.0.1
spring.mail.port=${random.int[4000,6000]}
And test configuration class which should start Wiser SMTP server and make it reusable in tests:
#Configuration
public class TestConfiguration {
#Autowired
private Wiser wiser;
#Value("${spring.mail.host}")
String smtpHost;
#Value("${spring.mail.port}")
int smtpPort;
#Bean
public Wiser provideWiser() {
// provide wiser for verification in tests
Wiser wiser = new Wiser();
return wiser;
}
#PostConstruct
public void initializeMailServer() {
// start server
wiser.setHostname(smtpHost);
wiser.setPort(smtpPort);
wiser.start();
}
#PreDestroy
public void shutdownMailServer() {
// stop server
wiser.stop();
}
}
Expected result is that application sends email using Wiser smtp server and verify number of sended messages.
But when I run service application throws MailSendException(Couldn't connect to host, port: 127.0.0.1, 4688; timeout -1;).
But when I add breakpoint and try connect using telnet smtp server allow to connect and don't throw Connection refused.
Do you have any idea why I can't test sending mails?
Full code preview is available on github:
https://github.com/karolrynio/demo-mail
I faced same problem. If using some constant port number for spring.mail.port in test Spring configuration combined with Maven tests forking, it resulted in tests randomly failing on port conflict when starting Wiser.
As noted here in comments, using random.int doesn't help - it returns different value each time it's referenced, and it's expected behavior (see this issue).
Hence, we need a different way to initialize spring.mail.port with a random value, so it would be constant within the test execution. Here's a way to do it (thanks for advice here):
First, we may not set spring.mail.port in test properties file at all. We'll initialize it in TestPropertySource. We'll need a class like this:
public class RandomPortInitailizer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
int randomPort = SocketUtils.findAvailableTcpPort();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext,
"spring.mail.port=" + randomPort);
}
}
Now we can run our tests this way (not too different from what's found in OP):
#RunWith(SpringRunner.class)
#ContextConfiguration(initializers = RandomPortInitailizer.class)
public class WhenEmailingSomeStuff {
#Value("${spring.mail.host}")
String smtpHost;
#Value("${spring.mail.port}")
int smtpPort;
#Before
public void startEmailServer() {
wiser = new Wiser();
wiser.setPort(smtpPort);
wiser.setHostname(smtpHost);
wiser.start();
}
#After
public void stopEmailServer() {
wiser.stop();
}
#Test
public void testYourJavaMailSenderHere() {
//
}
}
in the application properties can you also add
mail.smtp.auth=false
mail.smtp.starttls.enable=false
The change your code to have these extra two values
#Value("${mail.smtp.auth}")
private boolean auth;
#Value("${mail.smtp.starttls.enable}")
private boolean starttls;
and put these options in your initializeMailServer
Properties mailProperties = new Properties();
mailProperties.put("mail.smtp.auth", auth);
mailProperties.put("mail.smtp.starttls.enable", starttls);
wiser.setJavaMailProperties(mailProperties);
wiser.setHostname(smtpHost);
wiser.setPort(smtpPort);
wiser.start();
let me know if this worked for you