How to access Karate config parameters in JUnit tests? - java

Is there a way to access the config parameters from karate-config.js within JUnit tests?
Example:
karate-config.js
function fn() {
var env = karate.env; // get java system property 'karate.env'
karate.log('karate.env system property was:', env);
if (!env) {
env = 'dev'; // a custom 'intelligent' default
}
var config = { // base config JSON
appId: 'my.app.id',
appSecret: 'my.secret',
someUrlBase: 'https://some-host.com/v1/auth/',
anotherUrlBase: 'https://another-host.com/v1/'
};
if (env == 'stage') {
// over-ride only those that need to be
config.someUrlBase = 'https://stage-host/v1/auth';
} else if (env == 'e2e') {
config.someUrlBase = 'https://e2e-host/v1/auth';
}
// don't waste time waiting for a connection or if servers don't respond within 5 seconds
karate.configure('connectTimeout', 5000);
karate.configure('readTimeout', 5000);
return config;
}
MyTest.java
public class MyTest {
#Test
public void test() {
// How to access e.g. config.appId?
}
}

But why !?
There are multiple ways, but first - maybe you are over-engineering things and note that it is possible to read a *.properties file in Karate: properties.feature
You can also create a feature file with a single, empty Scenario - and call it from the Java API: https://github.com/intuit/karate#java-api
Map<String, Object> result = Runner.runFeature('classpath:foo.feature', null, true);
Which will give you the values of config in the returned Map.

if you need to call external javascript functions from java code I suggest you take a look at this

Related

How do you reference a function with the same name as a property?

Question
How can I reference this top-level function from within the data class? Or is Java's encapsulation of a class restrictive to the point that you cannot reach beyond the current class?
Code
def String branchName() {
return ((env.GIT_BRANCH ?: 'master') =~ /(?i)^(?:origin\/)?(.*)/)[0][1];
}
public DeployConfig implements IDeployConfig {
public DeployConfig(IDeployConfig config) {
this._appName = config.app;
this._gitUrl = config.gitUrl;
// ... et cetera
}
public String getBranchName() {
return branchName()
}
}
Background
I'm trying to define a data class that represents our standard Jenkinsfile configuration, in an attempt to make our pipeline more testable, and less "cross your fingers and hope it didn't break anything". Toward that goal, here is a snippet of that implementation.
Now, the property getter I'm trying to write doesn't know the actual branch being built when the object is constructed, because that's derived from the Map<String, String> returned by checkout scm which gets instantiated at runtime. We assign the GIT_BRANCH out to the global environment env.GIT_BRANCH so that it can be referenced elsewhere.
Miscellaneous
To the would-be suggestion of putting the target branch in the Jenkinsfile, that defeats the purpose of the Jenkinsfile being an instruction set for a job with Git configurations assigned, such as a multi-branch job with a shared Jenkinsfile.
Other Code
To give some context about what I mean about the checkout scm command happening after the construction of DeployConfig, the pipeline roughly resembles this:
// ./pipeline-library/vars/deploy.groovy
#!/usr/bin/groovy
def call(Closure body) {
def config = [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = config
body()
environmentVariables(config) // assigns certain keys to global env
if (env.IS_PROD) {
deployProd(config)
}
else {
deployNonProd(config)
}
}
// ./pipeline-library/vars/deployNonProd.groovy
#!/usr/bin/groovy
def call(Map config) {
// local variable declarations
pipeline {
agent {
label 'some-configuration-name'
}
environment {
// shared environment variables
}
options {
// configured options, like timestamps and log rotation
}
stages {
stage('Checkout') {
steps {
def gitInfo = checkout scm
env.GIT_BRANCH = gitInfo.GIT_BRANCH
}
}
// additional stages
}
}
}
Edits
Edit: The idea behind the property that calls the top-level function is a computed property that gets called later in the pipeline, after the checkout scm command has been executed. The DeployConfig would be constructed before the pipeline runs, and so the branch is not known at that time.
So I solved the problem for myself, but it's arguably a less than ideal solution to the problem. Basically, here's what I had to do:
First, I created a getter getGetBranchName and setter setGetBranchName on the class of type Closure<String> and it had a backing field _getBranchName. I also created a property getBranchName of type String that returned the result of this._getBranchName().
Second, if the incoming Map has a property branchName, then I set the value this._getBranchName = () -> { return config.branchName } so that I am referencing the getter of an outer object.
Third, as a final check, I assign the global function signature from Jenkins after constructing the DeployConfig object. That all looks like the below code (Note: ellipses are used to indicate more code unrelated to the specific solution):
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import com.domain.jenkins.data.DeployConfig
import com.domain.jenkins.data.GitUrl
import com.domain.jenkins.exceptions.InterruptException
import com.domain.jenkins.io.FileSystem
import com.domain.jenkins.pipeline.PipelineJob
import com.domain.jenkins.pipeline.PipelineStage
class Program {
static FileSystem fs
static PipelineJob pipeline
static Map<String, String> env
static Map jenkinsfile
static {
fs = new FileSystem()
pipeline = new PipelineJob()
env = [:]
jenkinsfile = [ ... ]
}
static String branchName() {
return ((env.GIT_BRANCH ?: 'master') =~ /(?i)^(?:origin\/)?(.*)/)[0][1]
}
static void main(String[] args) {
println 'Initialize pipeline'
pipeline = new PipelineJob()
println 'Initialize configuration'
DeployConfig config = new DeployConfig(jenkinsfile)
println new JsonBuilder(config.toMap()).toPrettyString()
println 'Assign static method as getBranchName method'
config.getBranchName = () -> { return branchName() }
println 'Assign environment variables to global env'
env << config.environmentVariables
...
}
}
And the DeployConfig class accepts that using the following (Note: most of the related code is not included for brevity's sake):
package com.domain.jenkins.data
import com.domain.jenkins.interfaces.IDeployConfig
import com.domain.jenkins.interfaces.IMappable
import com.domain.jenkins.interfaces.Mappable
class DeployConfig extends Mappable implements IDeployConfig, IMappable {
private Closure<String> _getBranchName
DeployConfig() {
this.branchName = 'master'
}
DeployConfig(Map options) {
this.branchName = options.branchName
}
String getBranchName() {
return this._getBranchName()
}
void setBranchName(String value) {
if(this._getBranchName == null) {
this._getBranchName = () -> { return 'master' }
}
if(value) {
this._getBranchName = () -> { return value }
}
}
void setGetBranchName(Closure<String> action) {
this._getBranchName = action
}
}

Configuration file depending on environment variables in Java EE

I have created a REST API with Jersey and I'm trying to figure out how to handle configuration files depending on the environment.
The main problem is that my web application is deployed on a Linux server so I cannot have the same paths between my development env on Windows and my staging and prods envs on Linux.
My configuration file is a basic XML file. Is there a way to add environment variables to that XML file and tell Java to replace these env var at runtime to their corresponding values? If it's possible, it would be possible to add an env var corresponding to the root path of all the paths in the config file that would be changed depending on a single env var.
Is there a better way to handle config files depending on environments?
There are many ways to externalize your configuration. You can take an example from Spring Boot, which currently supports 14 sources of configuration (cf. Externalized Configuration).
In your case you probably want to support points 5 to 9, which can easily be implemented in a JAX-RS application. For example you can use something like this:
#Path("/hello")
public class Hello {
#Context
private ServletConfig servletConfig;
public static String getProperty(ServletConfig config, String key) {
// ServletConfig
String value = config.getInitParameter(key);
// ServletContext
if (value == null) {
value = config.getServletContext().getInitParameter(key);
}
// JNDI
if (value == null) {
try {
value = InitialContext.doLookup("java:comp/env/" + key);
} catch (ClassCastException | NamingException e) {
// No JNDI
}
}
// Java system property
if (value == null) {
value = System.getProperty(key);
}
// OS environment variable
if (value == null) {
value = System.getenv(value);
}
return value;
}
#GET
public String sayHello(#PathParam("id") String who) {
return getProperty(servletConfig, "who");
}
}
You can also stick to one source of properties like ServerContext init parameters and override them in a context.xml file using Java system properties or OS Environment properties: Tomcat replaces the placeholders ${variable_name} in all its XML config files.

I can not test singleton without load() method

When I start the application, I need to load properties from different sources: war, file system, database, and JVM. I need to load properties once and use them within running my application. I do not need to refresh it. I don't have DI - it is a simple java application with singletons. I decide to create AppProperties singleton and load properties when starting the application. It is the best solution by the current time for me(I hope somebody makes the best solution). It is my Singleton:
import java.io.InputStream;
import java.util.Properties;
public class AppProperties {
private static AppProperties instance;
private Properties propertiesFromWar;
private Properties propertiesFromFile;
private Properties propertiesFromDB;
private AppProperties() {
propertiesFromWar = new Properties();
try {
propertiesFromWar.load(getPropertiesAsInputStreamFromWar());
propertiesFromFile.load(getPropertiesAsInputStreamFromFile());
propertiesFromDB.load(getPropertiesAsInputStreamFromDB());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private InputStream getPropertiesAsInputStreamFromDB() {
//some implementation
return null;
}
private InputStream getPropertiesAsInputStreamFromFile() {
//some implementation
return null;
}
private InputStream getPropertiesAsInputStreamFromWar() {
return getClass().getResourceAsStream("META-INF/application.properties");
}
public static AppProperties getInstance() {
if (instance == null) {
instance = new AppProperties();
}
return instance;
}
public String getProperty(String key) {
String value;
value = System.getProperty(key);
if (value == null) {
value = propertiesFromDB.getProperty(key);
if (value == null) {
value = propertiesFromFile.getProperty(key);
if (value == null) {
value = propertiesFromWar.getProperty(key);
}
}
}
return value;
}
}
But I do not understand, How can I use it in tests. Because I hardcode paths for aplication.properties files. And when I will create this instance in the tests, I will create AppProperties with real properties.
I tried to add a public method like load(filePath). But with this method, it will be not a singleton. If somebody will call this method in another place of application - my singleton will be reloaded with new data. Now I have 2 problems.
If I add load() method - it will be dangerous for reloading data. But I can use it in tests.
If I do not add this method - I can not test it.
P.S I read this article The Little Singleton
But I do not understand some moments. If I have singleton with private constructor, I can not extend it like in article.
In your test resources directory, create META-INF directory. Here create a file application.properties and add some properties for testing purposes in it.
Make sure the above directory is in the classpath when you will run the tests. This way, when getPropertiesAsInputStreamFromWar() is called, it will look for META-INF/application.properties in the classpath.
Being tests allow specifying JVM launch arguments, this can be "solved" pretty easily.
This also adds some flexibility.
java -DpropertiesPath="..." -jar yourJar.jar
And, adapting your code
private InputStream getPropertiesAsInputStreamFromWar() {
final String propertiesPath = Objects.requireNonNull(System.getProperty("propertiesPath"));
return getClass().getResourceAsStream(propertiesPath);
}
Instead of requireNonNull, you could use a default value, e.g.
META-INF/application.properties.

TestNG reporting issues due to multi-threading

I have a testNG framework integrated with testrails. Because of the restrictions around the testrail api, the framework was designed to collate test result in bulk and upload them all at once when the test run is complete.
To do so, I've created a BaseTest class that provides a variable id that each test method can set itself to match a corresponding test case in test rails. Once the test method assigns this variable, we pack it in to the result object:
public abstract class BaseTest {
protected static final ThreadLocal<Integer> testrailIds = new ThreadLocal();
protected int testRailCaseId = -1;
//TODO: set down a clear and strong process for handling environment/domain. Once that is in place, report it
// loudly so it is clear where the tests ran
protected String baseUrl = "";
protected static final Logger LOG = Logger.getLogger(BaseTest.class);
/**
* records the case ID stored at the individual test level inside the result set for the
* test so that we can access it later when reporting our results
* #param result the result context object
*/
#AfterMethod(alwaysRun = true)
public void afterMethod(ITestResult result) {
if(testRailCaseId == -1) {
LOG.warn("NO CASE ID HAS BEEN SET FOR THIS TEST");
}
result.setAttribute("case_id", testrailIds.get());
}
Once all tests have executed we build a request object in the afterSuite method and pipe both the test cases and test results to testrail.
for(ISuiteResult suiteResult: suite.getResults().values()) {
ctx = suiteResult.getTestContext();
for (ITestResult result : ctx.getPassedTests().getAllResults()) {
cases.add((int) result.getAttribute("case_id"));
JSONObject resultJson = new JSONObject();
resultJson.put("case_id", result.getAttribute("case_id"));
resultJson.put("status_id", 1);
payload.add(resultJson);
}
for (ITestResult result : ctx.getFailedTests().getAllResults()) {
cases.add((int) result.getAttribute("case_id"));
JSONObject resultJson = new JSONObject();
resultJson.put("case_id", result.getAttribute("case_id"));
resultJson.put("status_id", 5);
payload.add(resultJson);
}
}
// get a clean instance of the api
TestRailApi tr = new TestRailApi();
//now dump that arraylist into a json param and add it to the test run
tr.updateRun(runId, cases);
//once the test run has been created, clean up again and build the results request
tr = new TestRailApi();
tr.addResultsForCases(runId, payload);
the testRailCaseId is set at the beginning of each test method with a simple assignment
this.testRailCaseId = 491;
or
testrailIds.set(489);
This worked fine until we started using multi-threading. Now, the value of testRaidCaseId is being overwritten by parallel tests, resulting in smaller result sets than expected.
I've been attempting to manage the threads through a ThreadLocal (as seen in the code above), but have been unsuccessful so far -- values I try to set in the before method or in the tests are coming up empty in the after methods.
The test methods themselves are fine, my only struggle is with shared content being passed into them from the parent.
Anyone have any guidance for how to manage my variables across the baseTest through the test methods to ensure my various ids don't clobber each other?
Sample test case:
#Test
#Parameters({ "domain", "username", "password" })
public void logInAuthEmptyToken(#Optional("http://REDACTED.com") String domain, String username, String password) {
this.testRailCaseId = 385;
Map<String, String> loginInfo = BaseAuthTests.login(domain, username, password);
AuthServiceApi auth = new AuthServiceApi(domain);
auth.addAuthTokens("", loginInfo.get("arrival_key"), loginInfo.get("profile_key"));
auth.executeLogin(400);
}

Gradle plugin extension set a property of type Map

I want to set a Map of attributes to my plugin extension. So basically I want to write something like
settings {
envVars = {
a = "abc"
b = "dec"
...
n = "sdf"
}
}
When I use an attribute in my Extension class
private Map<?,?> envVars;
Gradle tells me that it can not set the property settings. So what I would like to achieve is to set a map of values in my extension class.
What I did achieve is to get the closure when i write the following:
settings {
envVars {
a = "abc"
b = "dec"
...
n = "sdf"
}
}
public class extension {
....
public envVars(Closure c){}
}
But then I have no clue what to do with the closure and how to access what is inside, so I would rather have a Map instead of the closure
Regards
Mathias
Ok, you just have to write the map properly :/
envVars = [test: 'test']
and everything is fine
I am using the following to read a map of values from build.gradle
reference: https://github.com/liquibase/liquibase-gradle-plugin
Container class:
class Database {
def name
def arguments = [logLevel: 'info']
Database(String name) {
this.name = name
}
Extension Class:
class MyExtension {
final NamedDomainObjectContainer<Database> databases
def databases(Closure closure){
databases.configure(closure)
}
def methodMissing(String name, args) {
arguments[name] = args[0]
}
}
Load Extensions
def databases = project.container(Database) { name ->
new Database(name)
}
project.configure(project) {
extensions.create("extensionName", MyExtension, databases)
}
Sample build.gradle:
dbDiff{
databases{
db1{
url 'testUrl'
}
}
}

Categories