I'm using MockWebServer library in my Android JUnit tests. I'm testing an SDK that makes calls to a server. So I'm using MockWebServer to override these server URLs and capture what the SDK is sending to make assertions on it.
The problem that I'm running into is that if I try to do server.takeRequest() and assign it to a new RecordedRequest variable, the test hangs up on the second server.takeRequest() and sometimes, even on the first one -- if I run it on an emulator it hangs on the first server.takeRequest() method but if I run it on my physical Android device, it freezes on the second server.takeRequest() method.
public void testSomething() {
final MockWebServer server = new MockWebServer();
try {
server.play();
server.enqueue(new MockResponse().setBody("")
.setResponseCode(HttpURLConnection.HTTP_INTERNAL_ERROR));
server.enqueue(new MockResponse().setBody("")
.setResponseCode(HttpURLConnection.HTTP_OK));
server.enqueue(new MockResponse().setBody("")
.setResponseCode(HttpURLConnection.HTTP_OK));
URL url = server.getUrl("/");
// This internal method overrides some of the hardcoded URLs
// within the SDK that I'm testing.
Util.overrideUrls(url.toString())
// Do some server calls via the SDK utilizing the mock server url.
RecordedRequest requestFor500 = server.takeRequest();
// Do some assertions with 'requestFor500'
// Do some more server calls via the SDK utilizing the mock server url.
/*
* This is the part where the JUnit test hangs or seems to go into
* an infinite loop and never recovers
*/
RecordedRequest requestAfter500Before200 = server.takeRequest();
} catch {
...
}
}
Am I doing something wrong or is this some type of bug with MockWebServer?
Add timeout to MockWebServer so that it does not hang
server.takeRequest(1, TimeUnit.SECONDS);
There seems to be a problem with MockWebServer's dispatch queue, which freezes for some reason when serving responses which are not 200 or 302. I have solved this by providing a custom dispatcher:
MockWebServer server = ...;
final MockResponse response = new MockResponse().setResponseCode(401);
server.setDispatcher(new Dispatcher() {
#Override
public MockResponse dispatch(RecordedRequest request)
throws InterruptedException {
return response; // this could have been more sophisticated
}
});
Tested with MockWebServer 2.0.0
Related
Often when I consider a new library or technology, I create a small POC or test program to get a feel for it. So I did with gRPC-Spring-Boot-Starter. A simple example code is posted below my question text.
This sample has been extended in complexity and eventually, the library has found its way into production code. So far it has survived many runs under moderate load.
Note that, naturally, the production service is not client to itself. But the production gRPC service is in fact client to other gRPC services.
Now I was thinking to write some kind of between-unit-and-integration test where I spin up local instances (starting with a single one) of those other gRPC services (pulling data from some static local resource, for example). Basically, this test code looks very much like the one posted below my question.
However - as soon as we poll for results in forEachRemaining(), the test ends up hanging: I suspect a deadlock in ClientCalls#waitAndDrain (io.grpc:grpc-stub).
The funny thing is - this does not happen if the client were created "manually", i.e. without utilizing the third-party Spring extension:
ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:9091")
.defaultLoadBalancingPolicy("round_robin")
.usePlaintext()
.build();
StockStaticDataRequestServiceBlockingStub stub = StockStaticDataRequestServiceGrpc.newBlockingStub(channel);
I use Spring Boot 2.6.3, gRPC-Spring-Boot-Starter 2.13.1, gRPC 1.44.0, proto 3.19.2 and netty 4.1.73, for what it is worth.
Now I wonder if someone here encountered similar issues or can give me some pointers while I am trying to figure out the inner workings of gRPC more.
Added sample project on GH.
The main branch contains the - maybe dubious - test setup I chose in the beginning, branches are some refinements, like using #Abhijit Sarkar's grpc-test library. Tests are green so far.
grpc:
client:
stocks:
address: 'static://localhost:9091'
enableKeepAlive: false
negotiationType: plaintext
server:
port: 9092
#SpringBootTest
class TestGrpc {
#GrpcClient("stocks")
private StockStaticDataRequestServiceBlockingStub stub;
#BeforeAll
public static void setUp() throws Exception {
final Server server = ServerBuilder
.forPort(9091)
.addService(new StockStaticDataRequestTestService())
.build();
server.start();
final Thread serverThread = new Thread(() -> {
try {
server.awaitTermination();
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
});
serverThread.setDaemon(false);
serverThread.start();
}
#Test
void testClient() {
StockStaticManyDataRequest request = StockStaticManyDataRequest.newBuilder()
.addAllTickerSymbols(List.of("AAPL"))
.build();
stub.getManyStockStatics(request).forEachRemaining(security -> {
LOG.info("security={}", security);
});
}
}
public class StockStaticDataRequestTestService extends StockStaticDataRequestServiceImplBase {
#Override
public void getManyStockStatics(StockStaticManyDataRequest request, StreamObserver<Security> responseObserver) {
responseObserver.onNext(Security.newBuilder()
.setSecurity("TEST-MANY")
.build());
responseObserver.onNext(Security.newBuilder()
.setSecurity("TEST-MORE")
.build());
responseObserver.onCompleted();
}
}
message Security {
string tickerSymbol = 1;
string security = 2;
}
message StockStaticManyDataRequest {
repeated string tickerSymbols = 1;
}
service StockStaticDataRequestService {
rpc getManyStockStatics(StockStaticManyDataRequest) returns (stream Security) {}
}
I think what the problem might be is that you should not be starting the server at all. There are some grpc-spring-boot-starter annotations that should be added to a test configuration class that will start / stop the server. See details here.
https://yidongnan.github.io/grpc-spring-boot-starter/en/server/testing.html#integration-tests
I also tried to make what you have work, but the server once started really won't shutdown. This makes the next test suite that runs fail due to port conflicts when it tries to start.
Here's my test class.
#Slf4j
#SpringBootTest
#ActiveProfiles("test")
#SpringJUnitConfig(classes = { ServiceIntegrationTestConfiguration.class })
#DirtiesContext
class TestGprc {
#GrpcClient("stocks")
private StockStaticDataRequestServiceBlockingStub stub;
/**
* #throws java.lang.Exception
*/
#BeforeAll
static void setUpBeforeClass() throws Exception {
log.info("setUpBeforeClass");
}
/**
* #throws java.lang.Exception
*/
#AfterAll
static void tearDownAfterClass() throws Exception {
log.info("tearDownAfterClass");
}
/**
* #throws java.lang.Exception
*/
#BeforeEach
void setUp() throws Exception {
}
/**
* #throws java.lang.Exception
*/
#AfterEach
void tearDown() throws Exception {
}
#Test
#DirtiesContext
void testClient() {
StockStaticManyDataRequest request = StockStaticManyDataRequest.newBuilder()
.addAllTickerSymbols(List.of("AAPL"))
.build();
stub.getManyStockStatics(request).forEachRemaining(security -> {
log.info("security={}", security);
});
}
}
Here's the configuration project.
#Configuration
#ImportAutoConfiguration({ GrpcServerAutoConfiguration.class, // Create required server beans
GrpcServerFactoryAutoConfiguration.class, // Select server implementation
GrpcClientAutoConfiguration.class,
GrpcStarterApplication.class})
public class ServiceIntegrationTestConfiguration {
// add mock beans here of needed.
}
My overrides for the properties. see application-test.yaml
grpc:
client:
stocks:
address: in-process:test
enableKeepAlive:
negotiationType:
server:
inProcessName: test
port: -1
I posted the entire maven project here:
https://github.com/aerobiotic/grpc-spring-starter
Simply clone it and mvn clean install :-)
As far as your dead-lock goes in your production code:
make sure you are calling onCompleted
check your catch blocks and make sure onError is being called and that there is logging happening.
It's possible that starting the server and not getting it shutdown is affecting something. Perhaps test code is connecting to a server from a previous test.
Hoping someone else is having the same issue as me, or has other ideas.
I'm currently running Play 1.4.x (not by choice), but also working on upgrading to play 1.5.x, though I verified the same issue happens on both versions.
I created a simple Functional Test that loads data via fixtures
My fixture for loading test data is like so
data.yml
User(testUser):
name: blah
AccessToken(accessToken):
user: testUser
token: foo
Data(testData):
user: testUser
...
I've created a controller to do something with the data like this, that has middleware for authentication check. The routes file will map something like /foo to BasicController.test
public class BasicController extends Controller{
#Before
public void doAuth(){
String token = "foo"; // Get token somehow from header
AccessToken token = AccessToken.find("token = ?", token).first(); // returns null;
// do something with the token
if(token == null){
//return 401
}
//continue to test()
}
public void test(){
User user = //assured to be logged-in user
... // other stuff not important
}
}
Finally I have my functional test like so:
public class BasicControllerTest extends FunctionalTest{
#org.junit.Before
public void loadFixtures(){
Fixtures.loadModels("data.yml");
}
#Test
public void doTest(){
Http.Request request = newRequest()
request.headers.put(...); // Add auth token to header
Http.Response response = GET(request, "/foo");
assertIsOk(response);
}
}
Now, the problem I'm running into, is that I can verify the token is still visible in the headers, but running AccessToken token = AccessToken.find("token = ?", token).first(); returns null
I verified in the functional test, before calling the GET method that the accessToken and user were created successfully from loading the fixtures. I can see the data in my, H2 in-memory database, through plays new DBBrowser Plugin in 1.5.x. But for some reason the data is not returned in the controller method.
Things I've tried
Ensuring that the fixtures are loaded only once so there is no race condition where data is cleared while reading it.
Using multiple ways of querying the database via nativeQuery jpql/hql query language and through plays native way of querying data.
Testing on different versions of play
Any help would be very much appreciated!
This issue happens on functional tests, because JPA transactions must be encapsulated in a job to ensure that the result of the transaction is visible in your method. Otherwise, since the whole functional test is run inside a transaction, the result will only visible at the end of the test (see how to setup database/fixture for functional tests in playframework for a similar case).
So you may try this:
#Test
public void doTest() {
...
AccessToken token = new Job<AccessToken>() {
#Override
public User doJobWithResult() throws Exception {
return AccessToken.find("token = ?", tokenId).first();
}
}.now().get();
....
}
Hoping it works !
I think I had a similar issue, maybe this helps someone.
There is one transaction for the functional test and a different transaction for the controller. Changes made in the test will only become visible by any further transaction if those changes were committed.
One can achieve this by closing and re-opening the transaction in the functional test like so.
// Load / Persist date here
JPA.em().getTransaction().commit(); // commit and close the transaction
JPA.em().getTransaction().begin(); // reopen (if you need it)
Now the data should be returned in the controller method.
So your test would look like this:
public class BasicControllerTest extends FunctionalTest{
#org.junit.Before
public void loadFixtures(){
Fixtures.loadModels("data.yml");
JPA.em().getTransaction().commit();
// JPA.em().getTransaction().begin(); reopen (if you need it)
}
#Test
public void doTest(){
Http.Request request = newRequest()
request.headers.put(...); // Add auth token to header
Http.Response response = GET(request, "/foo");
assertIsOk(response);
}
}
I did never try this with fixtures. But i would assume they run in the same transaction.
I'm working on a Spring 5 project and have some very special expectations with junit. Spring 5 now support junit multithreading and that definitely works very well, I'm now running my hundreds of tests into method parrallel multithreading. But I just setup recently my whole automatic mailing system which works like a charm but that's where it start to be problematic : I run a class that send all my mails to test them, and so they are being sent concurently. But as I just tried right now to test it with not only one email at a time but several, I get a strange SSL handshake error which I related to the fact that concurrent mail sending is not supported by most mail clients.
That's where goes my interrogation: how can I run all my test classes with parallel methods execution except for that email batch sending class?
Maybe I should think about a mail queue to avoid this kind of problem in live? Anyone has an idea?
By the way, in case you wonder I'm yet using gmail client to send mail as I didn't configured it yet for our live mail sending but it will be achieved using dedicated 1and1.fr smtp email client.
Thanks for your patience!
For those who feels interested about the solution, here is how I solved it:
I created a new Singleton class which would handle the queue :
public class EmailQueueHandler {
/** private Constructor */
private EmailQueueHandler() {}
/** Holder */
private static class EmailQueueHandlerHolder
{
/** unique instance non preinitialized */
private final static EmailQueueHandler INSTANCE = new EmailQueueHandler();
}
/** access point for unique instanciation of the singleton **/
public static EmailQueueHandler getInstance()
{
return EmailQueueHandlerHolder.INSTANCE;
}
private List<EmailPreparator> queue = new ArrayList<>();
public void queue(EmailPreparator email) {
waitForQueueHandlerToBeAvailable();
queue.add(email);
}
public List<EmailPreparator> getQueue()
{
waitForQueueHandlerToBeAvailable();
List<EmailPreparator> preparators = queue;
queue = new ArrayList<>();
return preparators;
}
// This method is used to make this handler thread safe
private synchronized void waitForQueueHandlerToBeAvailable(){}
}
I then created a CRON task using #Schedule annotation in my Scheduler bean in which I would correctly handle any mail sending fail.
#Scheduled(fixedRate = 30 * SECOND)
public void sendMailsInQueue()
{
List<EmailPreparator> queue = emailQueueHandler.getQueue();
int mailsSent = queue.size();
int mailsFailed = 0;
for(EmailPreparator preparator : queue)
{
try {
// And finally send the mail
emailSenderService.sendMail(preparator);
}
// If mail sending is not activated, mail sending function will throw an exception,
// Therefore we have to catch it and only throw it back if the email was really supposed to be sent
catch(Exception e)
{
mailsSent --;
// If we are not in test Env
if(!SpringConfiguration.isTestEnv())
{
mailsFailed ++;
preparator.getEmail().setTriggeredExceptionName(e.getMessage()).update();
// This will log the error into the database and eventually
// print it to the console if in LOCAL env
new Error()
.setTriggeredException(e)
.setErrorMessage(e.getClass().getName());
}
else if(SpringConfiguration.SEND_MAIL_ANYWAY_IN_TEST_ENV || preparator.isForceSend())
{
mailsFailed ++;
throw new EmailException(e);
}
}
}
log.info("CRON Task - " + mailsSent + " were successfuly sent ");
if(mailsFailed > 0)
log.warn("CRON Task - But " + mailsFailed + " could not be sent");
}
And then I called this mail queue emptyer methods at the end of each unit test in my #After annotated method to make sure it's called before I unit test the mail resulted. This way I'm aware of any mail sending fail even if it appear in PROD env and I'm also aware of any mail content creation failure when testing.
#After
public void downUp() throws Exception
{
proceedMailQueueManuallyIfNotAlreadySent();
logger.debug("After Test");
RequestHolder requestHolder = securityContextBuilder.getSecurityContextHolder().getRequestHolder();
// We check mails sending if some were sent
if(requestHolder.isExtResultsSent())
{
for(ExtResults results : requestHolder.getExtResults())
{
ExtSenderVerificationResolver resolver =
new ExtSenderVerificationResolver(
results,
notificationParserService
);
resolver.assertExtSending();
}
}
// Some code
}
protected void proceedMailQueueManuallyIfNotAlreadySent()
{
if(!mailQueueProceeded)
{
mailQueueProceeded = true;
scheduler.sendMailsInQueue();
}
}
I'm trying to figure out how to integrate an external API and run every integration test against it. I've been reading and looking at:
https://github.com/dropwizard/dropwizard/blob/master/dropwizard-example/src/test/java/com/example/helloworld/IntegrationTest.java
https://github.com/dropwizard/dropwizard/blob/master/docs/source/manual/testing.rst
but it looks like these are examples of testing local endpoints and not external ones. I would like to be able to test my api calls with JUnit tests. Currently I'm having to start up and run my app to make sure they're working.
This is the direction I'm currently exploring:
private Client client;
#Before
public void setUp() throws Exception {
client = ClientBuilder.newClient();
}
#After
public void tearDown() throws Exception {
client.close();
}
#Test
public void testHitApi() throws Exception {
client.target("https://api.github.com/users/" + getUser() + "/repos");
}
Any help would be much appreciated, thanks!
You need to make the api call to hit the endpoint.
doing just :
client.target("https://api.github.com/users/" + getUser() + "/repos")
returns a WebTarget .
you should ideally do something like:
client
.target("https://api.github.com/users/" + getUser() + "/repos")
.request()
.get() ; // for a get call
google for exact post/put/delete calls .
If you mean to run your integration tests against an external api or a separate running instance of your api.
testEnvironment = new Environment("Test environment", Jackson.newObjectMapper(),
null, new MetricRegistry(), null);
ObjectMapper mapper = Jackson.newObjectMapper(new YAMLFactory());
IntegrationTestConfiguration integrationTestConfiguration = mapper.readValue(fixture("integration-testing-config.yml"),
IntegrationTestConfiguration.class);
Instantiate your client as so
exampleClient = new exampleClient(testEnvironment, clientConfiguration);
Hope this helps.
I'm trying to run this functional test
public class JsonRenderTest extends FunctionalTest {
#Before
public void setup() {
Fixtures.deleteDatabase();
Fixtures.loadModels("data.yml");
}
#Test
public void testThatJsonRenderingWorks() {
Response response = GET("/recipe/1");
assertIsOk(response);
}
}
The action answering this call is this
public static void showRecipe(Long id){
Recipe recipe = Recipe.findById(id);
notFoundIfNull(recipe);
renderJSON(recipe);
}
When I run the test in firefox using the TestRunner at http://localhost:8080/#tests
I get this error message:
Failure, Response status expected:<200> but was:<404>
Now if I run this url http://localhost:8080/recipe/1 in a browser, I get the json responce I'm expecting wich is a json representation of my recipe object.
There is of course a recipe in the database with id 1.
Now here is my question. Why is the test failing when the browser is not. I tryed this in Chrome, IE and FF with the same result.
Any pointers would be much appreciated.
Thanks
-Alain
Thanks eveyone.
Ok I found the answer.
It appears that my test was running before the fixture data was fully loaded.
I was running my tests against a local MySql database.
When I removed the call
Fixtures.deleteDatabase();
The test was running fine.
To fix the problem I am now running my test against a mem database with this in the application.conf file
%test.db.url=jdbc:h2:mem:play;MODE=MYSQL;LOCK_MODE=0
You can use a helper method to add content type to your request
public Request jsonRequest(Request request) {
request.contentType = "application/json";
request.format = "json";
return request;
}
Then your test become
#Test
public void testThatJsonRenderingWorks() {
Response response = GET(jsonRequest(newRequest)), "/recipe/1");
assertIsOk(response);
}
}
You can also you WS classes to do real http tests insted.