Quarkus kubernetesMockServer for application initialization - unit-testing

I am working on a Quarkus application to acct as an Operator in a OpenShift/Kubernetes cluster. When writing the tests using a kubernetesMockServer it is working fine for REST calls to developed application but when code runs inside an Initialization Block it is failing, in the log I see that mock server is replying with a 404 error:
2020-02-17 11:04:12,148 INFO [okh.moc.MockWebServer] (MockWebServer /127.0.0.1:53048) MockWebServer[57577] received request: GET /apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions HTTP/1.1 and responded: HTTP/1.1 404 Client Error
On the TestCode I have:
#QuarkusTestResource(KubernetesMockServerTestResource.class)
#QuarkusTest
class TestAIRController {
#MockServer
KubernetesMockServer mockServer;
private CustomResourceDefinition crd;
private CustomResourceDefinitionList crdlist;
#BeforeEach
public void before() {
crd = new CustomResourceDefinitionBuilder()
.withApiVersion("apiextensions.k8s.io/v1beta1")
.withNewMetadata().withName("types.openshift.example-cloud.com")
.endMetadata()
.withNewSpec()
.withNewNames()
.withKind("Type")
.withPlural("types")
.endNames()
.withGroup("openshift.example-cloud.com")
.withVersion("v1")
.withScope("Namespaced")
.endSpec()
.build();
crdlist = new CustomResourceDefinitionListBuilder().withItems(crd).build();
mockServer.expect().get().withPath("/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions")
.andReturn(200, crdlist)
.always();
}
#Test
void test() {
RestAssured.when().get("/dummy").then().body("size()", Is.is(0));
}
}
The dummy rest is using the same code for searching the CRD, and in fact when running withouth the class observing the startup event it works fine
#Path("/dummy")
public class Dummy {
private static final Logger LOGGER =LoggerFactory.getLogger(Dummy.class);
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response listCRDs(){
KubernetesClient oc = new DefaultKubernetesClient();
CustomResourceDefinition crd = oc.customResourceDefinitions()
.list().getItems().stream()
.filter( ob -> ob.getMetadata().getName().equals("types.openshift.example-cloud.com"))
.findFirst().get();
LOGGER.info("CRD NAME is {}", crd.getMetadata().getName());
return Response.ok(new ArrayList<String>()).build();
}
}
Finally this is an except of the
#ApplicationScoped
public class AIRWatcher {
private static final Logger LOGGER = LoggerFactory.getLogger(AIRWatcher.class);
void OnStart(#Observes StartupEvent ev) {
KubernetesClient oc = new DefaultKubernetesClient();
CustomResourceDefinition crd = oc.customResourceDefinitions()
.list().getItems().stream()
.filter( ob -> ob.getMetadata().getName().equals("types.openshift.example-cloud.com"))
.findFirst().get();
LOGGER.info("Using {}", crd.getMetadata().getName());
}
}
It's like for some reason the mock server is still not initialized for the Startup event, is there any way to solve it?

The problem is that the Mock Server is only configured to respond right before the test execution, while this code:
void OnStart(#Observes StartupEvent ev) {
KubernetesClient oc = new DefaultKubernetesClient();
CustomResourceDefinition crd = oc.customResourceDefinitions()
.list().getItems().stream()
.filter( ob -> ob.getMetadata().getName().equals("types.openshift.example-cloud.com"))
.findFirst().get();
LOGGER.info("Using {}", crd.getMetadata().getName());
}
runs when the application is actually comes up (which is before any #BeforeEach runs).
Can you please open an issue on the Quarkus Github? This should be something we provide a solution for

Related

Redis unit test using redis mock

I'm using the following tool to run an embedded redis for unit testing.
In the beginning of my registrationService is creating a new instance of redis server.
#Import({RedisConfiguration.class})
#Service
public class RegistrationService
RedisTemplate redisTemplate = new RedisTemplate(); //<- new instance
public String SubmitApplicationOverview(String OverviewRequest) throws IOException {
. . .
HashMap<String,Object> applicationData = mapper.readValue(OverviewRequest,new TypeReference<Map<String,Object>>(){});
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
UUID Key = UUID.randomUUID();
redisTemplate.opsForHash().putAll(Key.toString(), (applicationData)); //<-- ERRORS HERE
System.out.println("Application saved:" + OverviewRequest);
return Key.toString();
}
}
And I'm starting a mock redis server in my Test below.
...
RedisServer redisServer;
#Autowired
RegistrationService RegistrationService;
#Before
public void Setup() {
redisServer = RedisServer.newRedisServer();
redisServer.start();
}
#Test
public void testSubmitApplicationOverview() {
String body = "{\n" +
" \"VehicleCategory\": \"CAR\",\n" +
" \"EmailAddress\": \"email#email.com\"\n" +
"}";
String result = RegistrationService.SubmitApplicationOverview(body);
Assert.assertEquals("something", result);
}
Redis settings in application.properties
#Redis Settings
spring.redis.cluster.nodes=slave0:6379,slave1:6379
spring.redis.url= redis://jx-staging-redis-ha-master-svc.jx-staging:6379
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=10.40.2.126:26379,10.40.1.65:26379
spring.redis.database=2
However, I'm getting a java.lang.NullPointerException error on the following line in my service under test (registrationService).
redisTemplate.opsForHash().putAll(Key.toString(), (applicationData));
According to the [redis-mock][1] documentation, creating an instance like this:
RedisServer.newRedisServer(); // bind to a random port
Will bind the instance to a random port. It looks like your code expects a specific port. I believe that you need to specify a port when you create the server by passing a port number like this:
RedisServer.newRedisServer(8000); // bind to a specific port

Unit Testing - Spring Boot App

I looked at this link : How to write a unit test for a Spring Boot Controller endpoint
I am planning to unit test my Spring Boot Controller. I have pasted a method from my controller below. When I use the approach mentioned in the link above , will the call that I have to service.verifyAccount(request) not be made? Are we just testing whether the controller accepts the request in format specified and returns response in format specfied apart from testing the HTTP status codes?
#RequestMapping(value ="verifyAccount", method = RequestMethod.POST, produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<VerifyAccountResponse> verifyAccount(#RequestBody VerifyAccountRequest request) {
VerifyAccountResponse response = service.verifyAccount(request);
return new ResponseEntity<VerifyAccountResponse>(response, HttpStatus.OK);
}
You can write unit test case
using
#RunWith(SpringJUnit4ClassRunner.class)
// Your spring configuration class containing the
#EnableAutoConfiguration
// annotation
#SpringApplicationConfiguration(classes = Application.class)
// Makes sure the application starts at a random free port, caches it throughout
// all unit tests, and closes it again at the end.
#IntegrationTest("server.port:0")
#WebAppConfiguration
make sure you configure all your server configuration like port/url
#Value("${local.server.port}")
private int port;
private String getBaseUrl() {
return "http://localhost:" + port + "/";
}
Then use code mentioned below
protected <T> ResponseEntity<T> getResponseEntity(final String
requestMappingUrl, final Class<T> serviceReturnTypeClass, final Map<String, ?>
parametersInOrderOfAppearance) {
// Make a rest template do do the service call
final TestRestTemplate restTemplate = new TestRestTemplate();
// Add correct headers, none for this example
final HttpEntity<String> requestEntity = new HttpEntity<String>(new
HttpHeaders());
// Do a call the the url
final ResponseEntity<T> entity = restTemplate.exchange(getBaseUrl() +
requestMappingUrl, HttpMethod.GET, requestEntity, serviceReturnTypeClass,
parametersInOrderOfAppearance);
// Return result
return entity;
}
#Test
public void getWelcomePage() {
Map<String, Object> urlVariables = new HashMap<String, Object>();
ResponseEntity<String> response = getResponseEntity("/index",
String.class,urlVariables);
assertTrue(response.getStatusCode().equals(HttpStatus.OK));
}

Salesforce Apex web service test class error

I'm working on Salesforce Apex web service to create record. Below is the apex web service class that I have written:
global class createCloudRecord {
global class projectInputs{
webService Integer ProjectID;
webService String ProjectName;
}
webService static Boolean createSFRecords(List<projectInputs> inputs) {
cv__Designation__c desg = new cv__Designation__c();
desg.cv__Active__c = true;
desg.cv__Default__c = false;
desg.cv__Description__c = 'Test Desc';
desg.OwnerId = '002B0000000K9soIAD';
desg.Name = inputs[0].ProjectName;
desg.cv__ExternalId__c = string.valueof(inputs[0].ProjectID);
insert desg;
return true;
}
}
It's working fine and creating records on SF cloud via SOAP API call. Now I have to written the test class for above web service with code coverage of min 75%.
Below is the code that I have written for my test class:
#isTest
private class createCloudRecordTest {
static testMethod void testCreateSFRecords() {
createCloudRecord.projectInputs project = new createCloudRecord.projectInputs();
project.ProjectID = 12345;
project.ProjectName = 'TestProject';
createCloudRecord.createSFRecords(project);
}
}
But this is showing an error for this line createCloudRecord.createSFRecords(project);:
Error: Compile Error: Method does not exist or incorrect signature.
Anyone has any idea how can I make this working.
Thanks!
I got the solution for my question. The problem was in my web service I've defined param as list but above in test class I'm passing param as single record.
So it should be something like below:
#isTest
private class createCloudRecordTest {
static testMethod void testCreateSFRecords() {
createCloudRecord.projectInputs project = new createCloudRecord.projectInputs();
project.ProjectID = 12345;
project.ProjectName = 'TestProject';
list<createCloudRecord.projectInputs> projects = new list<createCloudRecord.projectInputs>();
projects.add(project);
createCloudRecord.createSFRecords(projects);
}
}

How do I invoke Multiple Startup Projects when running a unit tests in Debug Mode

This seems like a simple thing to do but I can't seem to find any info anywhere! I've got a solution that has a service that we run in 'Console Mode' when debugging. I want it to be started and 'attached' when I run my unit test from Visual Studio.
I'm using Resharper as the unit test runner.
Not a direct answer to your question, BUT
We faced a similar problem recently and eventually settled on a solution using AppDomain
As your solution is already running as a Console project it would be little work to make it boot in a new AppDomain. Furthermore, you could run Assertions on this project as well as part of unit testing. (if required)
Consider the following static class Sandbox which you can use to boot multiple app domains.
The Execute method requires a Type which is-a SandboxAction. (class definition also included below)
You would first extend this class and provide any bootup actions for running your console project.
public class ConsoleRunnerProjectSandbox : SandboxAction
{
protected override void OnRun()
{
Bootstrapper.Start(); //this code will be run on the newly create app domain
}
}
Now to get your app domain running you simply call
Sandbox.Execute<ConsoleRunnerProjectSandbox>("AppDomainName", configFile)
Note you can pass this call a config file so you can bootup your project in the same fashion as if you were running it via the console
Any more questions please ask.
public static class Sandbox
{
private static readonly List<Tuple<AppDomain, SandboxAction>> _sandboxes = new List<Tuple<AppDomain, SandboxAction>>();
public static T Execute<T>(string friendlyName, string configFile, params object[] args)
where T : SandboxAction
{
Trace.WriteLine(string.Format("Sandboxing {0}: {1}", typeof (T).Name, configFile));
AppDomain sandbox = CreateDomain(friendlyName, configFile);
var objectHandle = sandbox.CreateInstance(typeof(T).Assembly.FullName, typeof(T).FullName, true, BindingFlags.Default, null, args, null, null, null);
T sandBoxAction = objectHandle.Unwrap() as T;
sandBoxAction.Run();
Tuple<AppDomain, SandboxAction> box = new Tuple<AppDomain, SandboxAction>(sandbox, sandBoxAction);
_sandboxes.Add(box);
return sandBoxAction;
}
private static AppDomain CreateDomain(string name, string customConfigFile)
{
FileInfo info = customConfigFile != null ? new FileInfo(customConfigFile) : null;
if (!string.IsNullOrEmpty(customConfigFile) && !info.Exists)
throw new ArgumentException("customConfigFile not found using " + customConfigFile + " at " + info.FullName);
var appsetup = new AppDomainSetup();
//appsetup.ApplicationBase = Path.GetDirectoryName(typeof(Sandbox).Assembly.Location);
appsetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
if (customConfigFile==null)
customConfigFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
appsetup.ConfigurationFile = customConfigFile;
var sandbox = AppDomain.CreateDomain(
name,
AppDomain.CurrentDomain.Evidence,
appsetup);
return sandbox;
}
public static void DestroyAppDomainForSandbox(SandboxAction action)
{
foreach(var tuple in _sandboxes)
{
if(tuple.Second == action)
{
AppDomain.Unload(tuple.First);
Console.WriteLine("Unloaded sandbox ");
_sandboxes.Remove(tuple);
return;
}
}
}
}
[Serializable]
public abstract class SandboxAction : MarshalByRefObject
{
public override object InitializeLifetimeService()
{
return null;
}
public void Run()
{
string name = AppDomain.CurrentDomain.FriendlyName;
Log.Info("Executing {0} in AppDomain:{1} thread:{2}", name, AppDomain.CurrentDomain.Id, Thread.CurrentThread.ManagedThreadId);
try
{
OnRun();
}
catch (Exception ex)
{
Log.Error(ex, "Exception in app domain {0}", name);
throw;
}
}
protected abstract void OnRun();
public virtual void Stop()
{
}
}

mysterious console output to stderr from jetty?

When running my embedded jetty web app launcher, I see the following output to stderr. I just started seeing this after moving my build to maven-2. Has anyone seen this before?
IDLE SCEP#988057 [d=false,io=1,w=true,rb=false,wb=false],NOT_HANDSHAKING, in/out=0/0 Status = OK HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 5469 bytesProduced = 5509
It repeats occasionally seemingly at random times.
This seems to be coming from jetty NIO support -- it appears that jetty feels it is appropriate to log to stderr when it close idle connections.
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.checkIdleTimestamp(SelectChannelEndPoint.java:231)
at org.eclipse.jetty.io.nio.SelectorManager$SelectSet$2.run(SelectorManager.java:768)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:436)
For those with similar problems, I overrode System.err with a mock output stream:
public class DebugOutputStream extends OutputStream {
private Logger s_logger = LoggerFactory.getLogger(DebugOutputStream.class);
private final OutputStream m_realStream;
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private Pattern m_searchFor;
public DebugOutputStream(OutputStream realStream, String regex) {
m_realStream = realStream;
m_searchFor = Pattern.compile(regex);
}
public void write(int b) throws IOException {
baos.write(b);
if (m_searchFor.matcher(baos.toString()).matches()) {
s_logger.info("unwanted output detected", new RuntimeException());
}
if (b == '\n') baos.reset();
m_realStream.write(b);
}
}