I am using
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<version>5.11.2</version>
<scope>test</scope>
</dependency>
for integration tests of a REST API. I've started with very basic expectations, to further ellaborate the tests later, once the minimum stuff passes the test. To my surpise, MockServer keeps telling me that no received requests match my expectations.
I am using the Java API, to write tests that use Mockito and PowerMock to deal with static methods. TestNG is the Test Freamework.
This is my code:
#PowerMockIgnore({"javax.xml.parsers.*", "org.apache.logging.log4j.*", "com.sun.org.apache.*", "sun.security.*", "javax.net.ssl.*"})
#PrepareForTest({K8sTarget.class, K8sApi.class})
public class DataAccessImplTest extends PowerMockTestCase {
private static final String HTTP_METHOD_GET = "GET";
private static final String USER_ID= "46756123123";
private static final String USERS_PATH = "/api/v1/users/%s";
private static final String CONTENT_TYPE_APP_JSON = "application/json";
#Mock
Target mockTarget;
#Mock
K8sClient mockK8sClient;
private DataAccessFactory dataAccessFactory;
private DataAccessImpl dataAccessUT;
private MockServerClient mockServer;
AutoCloseable closeable;
#BeforeClass
public void setup() {
// ensure all connection using HTTPS will use the SSL context defined by
// MockServer to allow dynamically generated certificates to be accepted
HttpsURLConnection.setDefaultSSLSocketFactory(
new KeyStoreFactory(new MockServerLogger()).sslContext().getSocketFactory());
this.mockServer = startClientAndServer(PortFactory.findFreePort());
this.closeable = MockitoAnnotations.openMocks(this);
dataAccessFactory = DataAccessFactory.getInstance();
assertNotNull(dataAccessFactory);
PowerMockito.mockStatic(K8sApi.class);
PowerMockito.mockStatic(K8sTarget.class);
PowerMockito.when(K8sApi.getK8sClient()).thenReturn(mockK8sClient);
PowerMockito.when(K8sTarget.of(Mockito.any(K8sClient.class), Mockito.any(Target.class))).thenReturn(mockTarget);
Mockito.when(mockTarget.getName()).thenReturn("localhost");
Mockito.when(mockTarget.getPort()).thenReturn(this.mockServer.getPort().intValue());
dataAccessUT = dataAccessFactory.createDataClient();
}
#BeforeMethod
public void prepareMocks() {
Mockito.when(mockTarget.getName()).thenReturn("localhost");
Mockito.when(mockTarget.getPort()).thenReturn(this.mockServer.getPort().intValue());
}
#AfterClass
public void teardown() throws Exception {
this.closeable.close();
this.mockServer.stop();
}
#Test
public void getUserTest_200_Ok() throws IOException {
dataAccessUT.getUserData(USER_ID);
mockServer.when(request()
.withMethod(HTTP_METHOD_GET)
.withPath(String.format(USERS_PATH, USER_ID))
)
.respond(
response()
.withStatusCode(HttpStatusCode.OK_200.code())
.withHeader(HttpHeaderNames.CONTENT_TYPE.toString(), CONTENT_TYPE_APP_JSON)
.withBody("some_response_body")
);
}
}
and these are the console logs:
10:06:24.067 [nioEventLoopGroup-2-1] DEBUG com.commonlibrary.httpclient.common.HttpConnectionListener:28 - 0.1 HttpConnectionListener::operationComplete: connected to [localhost:58136] from [/127.0.0.1:58204]
10:06:24.285 [MockServer-EventLog0] INFO org.mockserver.log.MockServerEventLog:108 - 58136 received request:
{
"method" : "GET",
"path" : "/api/v1/users/46756123123",
"headers" : {
"authorization" : [ "Bearer token" ],
"accept" : [ "application/json" ],
"host" : [ "localhost:58136" ],
"content-length" : [ "0" ]
},
"keepAlive" : true,
"secure" : false
}
10:06:24.350 [MockServer-EventLog0] INFO org.mockserver.log.MockServerEventLog:108 - 58136 no expectation for:
{
"method" : "GET",
"path" : "/api/v1/users/46756123123",
"headers" : {
"authorization" : [ "Bearer token" ],
"accept" : [ "application/json" ],
"host" : [ "localhost:58136" ],
"content-length" : [ "0" ]
},
"keepAlive" : true,
"secure" : false
}
returning response:
{
"statusCode" : 404,
"reasonPhrase" : "Not Found"
}
10:06:24.483 [MockServer-EventLog0] INFO org.mockserver.log.MockServerEventLog:108 - 58136 stopped for port: 58136
As you can see (unless I am missing something) request should match the expectation, but it doesn'. I have tried several things, all of them without success:
reduce the request expectation to the bare minimum, just calling request() without defining anything else. This shouls match EVERY incoming request. Same result.
introduce Times.exactly(1) in the expectation. Same result.
specify the headers I am sending in the request, even though my understanding is that if they are not set in the expectation, they are not used for matching. Same result.
After 2 days, I am running out of ideas, so any help or hint would be appreciated. Thanks!
Edition after following hint and checking code examples in MockServer site
Following #peter-rowth suggestion, I moved the request sent after creating expectations and it worked.
I am editing this issue also to make clear that it duplicates [https://stackoverflow.com/questions/63843619/mockserver-request-not-found][1], that I found later.
It looks like in your test you are creating the expectations in MockServer after executing the call to your code under test? The fact that your console output from MockServer does not output matched/not matched expectations (the default behavior) indicated to me that there are no expectations setup when the web request is made to MockServer and a 404 is default response by MockServer when there is no expectation for a request.
Try adding that expectation as the first line in your test.
Related
My wiremock doesn't seem to work with autoconfigure. I have json files in a folder named stubs in the classpath and I ran the standalone jar on the port 8080.
#AutoConfigureWireMock(stubs="classpath:/stubs", port = 0)
public class TestResource {
#Autowired
private Service service;
#Test
public void contextLoads() throws Exception {
assertThat(this.service.go()).isEqualTo("Hello World!");
}
}
Example of a json file
{
"request" : {
"url" : "/api/users",
"method" : "GET",
"bodyPatterns" : [ {
"contains" : "some soap body"
}]
},
"response" : {
"status" : 200,
"body" : "Hello World",
"headers" : {
"X-Application-Context" : "application:-1",
"Content-Type" : "text/plain"
}
}
}
When I launch a request with GET -> localhost:8080/api/users/ It doesn't match with the json file.
Thanks in advance
I simply added all my json with a POST request on localhost:8080/__admin/mappings/import
http://wiremock.org/docs/stubbing/
As I'm only interested in checking some of the values from the response for my Contract Test, I want to leave out those fields that are not important to me. Thing is that when I run the test, generated test file checks if body defined in the test.groovy file is identical to the one generated from controller. So, my question is if there's a way to just assert those values that I'm interested in.
test.groovy
Contract.make {
request {
method 'GET'
urlPath('/api/node') {
queryParameters {
parameter('number', '123')
parameter('size', '1')
parameter('status', 'ACTIVE')
}
}
headers {
header("key", "someKey")
}
}
response {
status 200
headers {
contentType applicationJson()
}
body("""
"content":[
{
"id": "123456",
"status":"ACTIVE",
"details":[
{
"object":{
"id":"ccId",
"codeOne":"1",
"codeTwo":"2",
"codeThree":"3",
"_links":{
"self":{
"href":"https://url.com"
},
"style":{
"href":"https://universal-url.com"
},
"universalStyle":{
"href":"https://universal-style-v1-url.com"
}
}
}
"code":null
}
]
This is the error I'm getting
org.junit.ComparisonFailure: expected:<"[content]"> but was:
<"[{"links":[
{"rel":"first","href":"http://localhost/api/node?number=123&status=ACTIVE&page=0&size=1"},
{"rel":"prev","href":"http://localhost/api/node?number=123&status=ACTIVE&page=0&size=1"},
{"rel":"self","href":"http://localhost/api/node?number=123&size=1&status=ACTIVE"},
{"rel":"last","href":"http://localhost/api/node?number=123&status=ACTIVE&page=1&size=1"}],
"content":[{"id":"123456",...
In this case, I don't want to verify any related to "links" array. Is there any configuration I can use in order to bypass it?
Just don't put them in the response and they won't get generated.
I am new to using Swagger Codegen so I assume I am doing something completely wrong. I have a remote swagger.json that I am using to generate a java client. Everything works and looks good but the example usages in the readme are not referencing any API key.
I don't have access to the remote API that I am using just trying to create a nice Java SDK for interfacing with it.
This is what I am using to generate the code.
java -jar C:/Development/codegen/swagger-codegen-cli.jar generate
-a "client_id:XXXXXXXXXX,client_secret:YYYYYYYYYYYYY"
-i https://BLAH/swagger/v1/swagger.json -l java
-o C:/Development/workspace/JavaClient
-c C:/Development/workspace/JavaClient/java-genconfig.json
Then the example that get's generated in the read me looks something like this...
public static void main(String[] args) {
BusinessGroupApi apiInstance = new BusinessGroupApi();
Integer businessGroupId = 56; // Integer | The ID of the business group to fetch.
String apiVersion = "1.0"; // String | The requested API version
try {
BusinessGroup result = apiInstance.getBusinessGroupAsync(businessGroupId, apiVersion);
System.out.println(result);
} catch (ApiException e) {
System.err.println("Exception when calling BusinessGroupApi#getBusinessGroupAsync");
e.printStackTrace();
}
}
Side Note
When I try running the example, I get an error saying
Failed to connect to localhost/0:0:0:0:0:0:0:1:443
Is there some setting that I need to set so it looks for the service at a remote location instead of locally?
EDIT
I realized that I needed to modify their swagger as they might not have everything fleshed out.
Swagger Codegen Version = 2.4.5
swagger.json (Excluded the paths and definitions) I also downloaded thiers locally and added some more info to make it generate more info. I added host, basePath, schemes, consumes, produces, security and securityDefinitions.
{
"swagger": "2.0",
"info": {
"version": "1.0",
"title": "removed"
},
"host": "apitest.removed.com",
"basePath": "/removed/api",
"schemes": [
"https"
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"security": [
{
"clientId": []
},
{
"clientSecret": []
}
],
"securityDefinitions": {
"clientId": {
"type": "apiKey",
"in": "header",
"name": "client_id"
},
"clientSecret": {
"type": "apiKey",
"in": "header",
"name": "client_secret"
}
}
}
This actually updated the readme to look how I would expect it to look
ApiClient defaultClient = Configuration.getDefaultApiClient();
// Configure API key authorization: client_id
ApiKeyAuth client_id = (ApiKeyAuth) defaultClient.getAuthentication("client_id");
client_id.setApiKey("YOUR API KEY");
// Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null)
//client_id.setApiKeyPrefix("Token");
// Configure API key authorization: client_secret
ApiKeyAuth client_secret = (ApiKeyAuth) defaultClient.getAuthentication("client_secret");
client_secret.setApiKey("YOUR API KEY");
// Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null)
//client_secret.setApiKeyPrefix("Token");
BusinessGroupApi apiInstance = new BusinessGroupApi();
Integer businessGroupId = 56; // Integer | The ID of the business group to fetch.
String apiVersion = "1.0"; // String | The requested API version
try {
BusinessGroup result = apiInstance.getBusinessGroupAsync(businessGroupId, apiVersion);
System.out.println(result);
} catch (ApiException e) {
System.err.println("Exception when calling BusinessGroupApi#getBusinessGroupAsync");
e.printStackTrace();
}
Yet when I plug in my client_id and client_secret and try to run it I get a Exception in thread "main" java.lang.NullPointerException
at removed.Tester.main(Tester.java:18). I think I might have the security definitions not set up correctly.
It's not OAuth but they require a client_id and client_secret to be passed in the header parameters.
EDIT 2
Exception when calling BusinessGroupApi#getBusinessGroupAsync
package.client.ApiException:
at package.client.ApiClient.handleResponse(ApiClient.java:927)
at package.client.ApiClient.execute(ApiClient.java:843)
at package.client.api.BusinessGroupApi.getBusinessGroupAsyncWithHttpInfo(BusinessGroupApi.java:148)
at package.client.api.BusinessGroupApi.getBusinessGroupAsync(BusinessGroupApi.java:133)
at Tester.main(Tester.java:30)
Line 30 -> BusinessGroup result = apiInstance.getBusinessGroupAsync(businessGroupId, apiVersion);
EDIT 3
I enabled debugging with
defaultClient.setDebugging(true);
It looks like it's working but for some reason doesn't like my client_id and client_secret despite them both being the ones I used in postman.
--> GET <removed>/api/api/businessGroups/56?api-version=1.0 HTTP/1.1
Accept: application/json
client_secret: <removed>
client_id: <removed>
User-Agent: Swagger-Codegen/1.0-SNAPSHOT/java
--> END GET
<-- HTTP/1.1 403 (393ms)
Content-Length: 80
Content-Type: application/json
Date: Mon, 10 Jun 2019 20:05:07 GMT
QL-LB-Appliance: <removed>
QL-LB-Pool: <removed>
QL-LB-Server: <removed>
OkHttp-Sent-Millis: <removed>
OkHttp-Received-Millis: <removed>
{ "error": "invalid_client", "description": "wrong client_id or client_secret" }
<-- END HTTP (80-byte body)
Exception when calling BusinessGroupApi#getBusinessGroupAsync
<removed>.client.ApiException:
at <removed>.client.ApiClient.handleResponse(ApiClient.java:927)
at <removed>.client.ApiClient.execute(ApiClient.java:843)
at <removed>.client.api.BusinessGroupApi.getBusinessGroupAsyncWithHttpInfo(BusinessGroupApi.java:148)
at <removed>.client.api.BusinessGroupApi.getBusinessGroupAsync(BusinessGroupApi.java:133)
at Tester.main(Tester.java:32)
I have implemented a basic WireMock with a sample REST/HTTP request simulation. The server code implemented as below.
With this code, I get the following error when I issue the GET request from Postman (i.e. GET http://127.0.0.1:8089/some/thing).
No response could be served as there are no stub mappings in this WireMock instance.
What is missing in my setup/code?
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
public class MockApp {
private WireMockServer wireMockServer;
public MockApp(String testSpec) {
wireMockServer = new WireMockServer(WireMockConfiguration.options().
port(8089).
usingFilesUnderDirectory(testSpec).
disableRequestJournal());
}
public void start() {
wireMockServer.start();
}
public void stop() {
wireMockServer.stop();
}
}
The main function is:
public class MockMain {
public static void main(String[] args) {
String baseDir = System.getProperty("user.dir");
String testResource = baseDir + "/resources/testconfig/";
MockAMS mockAMS = new MockAMS(testResource);
mockAMS.start();
}
}
Under 'resources/testconfig', there is a file called mapping.json containing:
{
"request": {
"method": "GET",
"url": "/some/thing"
},
"response": {
"status": 200,
"body": "Hello world!",
"headers": {
"Content-Type": "text/plain"
}
}
}
I found solution for this. So, basically we need create a folder called "mappings" (exact name) under the directory identified by "testResource" variable. So in above code example, the mapping.json file will be stored at location: "MockApp/resources/testconfig/mappings/mapping.json".
Once this, is done, it will print the following output. As can be seen in the logs, "Stub mapping size is 1". This will be printed once you add the following line in the code.
System.out.println("Stub mapping size: " + wireMockServer.getStubMappings().size());
Stub mapping size: 1
{
"id" : "da5735a6-b6cc-45aa-8256-fb88b5670610",
"request" : {
"url" : "/some/thing",
"method" : "GET"
},
"response" : {
"status" : 200,
"body" : "Hello world!",
"headers" : {
"Content-Type" : "text/plain"
}
},
"uuid" : "da5735a6-b6cc-45aa-8256-fb88b5670610"
}
Does anyone have an example of using Apache Qpid within a standalone junit test.
Ideally I want to be able to create a queue on the fly which I can put/get msgs within my test.
So I'm not testing QPid within my test, I'll use integration tests for that, however be very useful to test methods handling msgs with having to mock out a load of services.
Here is the setup method I use for QPID 0.30 (I use this in a Spock test but should be portable to Java of Junit with no problems). This supports SSL connection, the HTTP management, and uses only in-memory startup. Startup time is sub-second. Configuration for QPID is awkward compared to using ActiveMQ for the same purpose, but QPID is AMQP compliant and allows for a smooth, neutral testing for AMQP clients (obviously the use of exchanges can not mimic RabbitMQs implementation, but for basic purposes it is sufficient)
First I created a minimal test-config.json which I put in the resources folder:
{
"name": "${broker.name}",
"modelVersion": "2.0",
"defaultVirtualHost" : "default",
"authenticationproviders" : [ {
"name" : "passwordFile",
"type" : "PlainPasswordFile",
"path" : "${qpid.home_dir}${file.separator}etc${file.separator}passwd",
"preferencesproviders" : [{
"name": "fileSystemPreferences",
"type": "FileSystemPreferences",
"path" : "${qpid.work_dir}${file.separator}user.preferences.json"
}]
} ],
"ports" : [ {
"name" : "AMQP",
"port" : "${qpid.amqp_port}",
"authenticationProvider" : "passwordFile",
"keyStore" : "default",
"protocols": ["AMQP_0_10", "AMQP_0_8", "AMQP_0_9", "AMQP_0_9_1" ],
"transports" : [ "SSL" ]
}, {
"name" : "HTTP",
"port" : "${qpid.http_port}",
"authenticationProvider" : "passwordFile",
"protocols" : [ "HTTP" ]
}],
"virtualhostnodes" : [ {
"name" : "default",
"type" : "JSON",
"virtualHostInitialConfiguration" : "{ \"type\" : \"Memory\" }"
} ],
"plugins" : [ {
"type" : "MANAGEMENT-HTTP",
"name" : "httpManagement"
}],
"keystores" : [ {
"name" : "default",
"password" : "password",
"path": "${qpid.home_dir}${file.separator}keystore.jks"
}]
}
I
I also needed to create a keystore.jks file for localhost because the QPID broker and the RabbitMQ client do not like to communicate over an unencrypted channel. I also added a file called "passwd" in "integTest/resources/etc" that has this content:
guest:password
Here is the code from the unit test setup:
class level variables:
def tmpFolder = Files.createTempDir()
Broker broker
def amqpPort = PortFinder.findFreePort()
def httpPort = PortFinder.findFreePort()
def qpidHomeDir = 'src/integTest/resources/'
def configFileName = "/test-config.json"
code for the setup() method:
def setup() {
broker = new Broker();
def brokerOptions = new BrokerOptions()
File file = new File(qpidHomeDir)
String homePath = file.getAbsolutePath();
log.info(' qpid home dir=' + homePath)
log.info(' qpid work dir=' + tmpFolder.absolutePath)
brokerOptions.setConfigProperty('qpid.work_dir', tmpFolder.absolutePath);
brokerOptions.setConfigProperty('qpid.amqp_port',"${amqpPort}")
brokerOptions.setConfigProperty('qpid.http_port', "${httpPort}")
brokerOptions.setConfigProperty('qpid.home_dir', homePath);
brokerOptions.setInitialConfigurationLocation(homePath + configFileName)
broker.startup(brokerOptions)
log.info('broker started')
}
code for cleanup()
broker.shutdown()
To make an AMQP connection from a Rabbit MQ client:
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://guest:password#localhost:${amqpPort}");
factory.useSslProtocol()
log.info('about to make connection')
def connection = factory.newConnection();
//get a channel for sending the "kickoff" message
def channel = connection.createChannel();
The Qpid project has a number of tests that use an embedded broker for testing. Whilst we use a base case to handle startup shutdown you could do the following to simply integrate a broker within your tests:
public void setUp()
{
int port=1;
// Config is actually a Configuaration File App Registry object, or Configuration Application Registry.
ApplicationRegistry.initialise(config, port);
TransportConnection.createVMBroker(port);
}
public void test()
{...}
public void tearDown()
{
TransportConnection.killVMBroker(port);
ApplicationRegistry.remove(port);
}
Then for the connection you need to specify the conectionURL for the broker. i.e. borkerlist='vm://1'
My solution on qpid-broker # 6.1.1, add below to pom.xml
<dependency>
<groupId>org.apache.qpid</groupId>
<artifactId>qpid-broker</artifactId>
<version>6.1.1</version>
<scope>test</scope>
</dependency>
qpid config file as:
{
"name" : "${broker.name}",
"modelVersion" : "6.1",
"defaultVirtualHost" : "default",
"authenticationproviders" : [ {
"name" : "anonymous",
"type" : "Anonymous"
} ],
"ports" : [ {
"name" : "AMQP",
"port" : "${qpid.amqp_port}",
"authenticationProvider" : "anonymous",
"virtualhostaliases" : [ {
"name" : "defaultAlias",
"type" : "defaultAlias"
} ]
} ],
"virtualhostnodes" : [ {
"name" : "default",
"type" : "JSON",
"defaultVirtualHostNode" : "true",
"virtualHostInitialConfiguration" : "{ \"type\" : \"Memory\" }"
} ]
}
code to start the qpid server
Broker broker = new Broker();
BrokerOptions brokerOptions = new BrokerOptions();
// I use fix port number
brokerOptions.setConfigProperty("qpid.amqp_port", "20179");
brokerOptions.setConfigurationStoreType("Memory");
// work_dir for qpid's log, configs, persist data
System.setProperty("qpid.work_dir", "/tmp/qpidworktmp");
// init config of qpid. Relative path for classloader resource or absolute path for non-resource
System.setProperty("qpid.initialConfigurationLocation", "qpid/qpid-config.json");
brokerOptions.setStartupLoggedToSystemOut(false);
broker.startup(brokerOptions);
code to stop qpid server
broker.shutdown();
Since I use anonymouse mode, client should do like:
SaslConfig saslConfig = new SaslConfig() {
public SaslMechanism getSaslMechanism(String[] mechanisms) {
return new SaslMechanism() {
public String getName() {
return "ANONYMOUS";
}
public LongString handleChallenge(LongString challenge, String username, String password) {
return LongStringHelper.asLongString("");
}
};
}
};
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(20179);
factory.setSaslConfig(saslConfig);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
That's all.
A little more on how to do it on other version.
You can download qpid-broker binary package from official site. After download and unzip, you can run it to test as server against your case. After your case connected server well, using commandline to generate or just copy the initial config file in QPID_WORK, remove useless id filed and use it for embedded server like above.
The most complicated thing is the authentication. You can choose PLAIN mode but you have to add the username and password in initial config. I choose anonymous mode which need a little code when connecting. For other authentication mode you have specify the password file or key/cert store, which I didnt try.
If it still not working, you can read the qpid-borker doc and Main class code in qpid-broker artifact which show how command line works for each settings.
The best I could figure out was:
PropertiesConfiguration properties = new PropertiesConfiguration();
properties.addProperty("virtualhosts.virtualhost.name", "test");
properties.addProperty("security.principal-databases.principal-database.name", "testPasswordFile");
properties.addProperty("security.principal-databases.principal-database.class", "org.apache.qpid.server.security.auth.database.PropertiesPrincipalDatabase");
ServerConfiguration config = new ServerConfiguration(properties);
ApplicationRegistry.initialise(new ApplicationRegistry(config) {
#Override
protected void createDatabaseManager(ServerConfiguration configuration) throws Exception {
Properties users = new Properties();
users.put("guest","guest");
users.put("admin","admin");
_databaseManager = new PropertiesPrincipalDatabaseManager("testPasswordFile", users);
}
});
TransportConnection.createVMBroker(ApplicationRegistry.DEFAULT_INSTANCE);
With a URL of:
amqp://admin:admin#/test?brokerlist='vm://:1?sasl_mechs='PLAIN''
The big pain is with configuration and authorization. Milage may vary.