Testing AbstractProcessor (Java's annotation handling at compile time) - java

I am writing a library which relies on the AbstractProcessor class, since I want to write a great library I want to have a good coverage too. Since the preprocessor works at the compile time I am not sure how to test that code.
I have some test scenarios in mind where the build should fail. However how can I test that? Do I have to execute gradle and check the exit code? Is there a clean way to verify that the build fails caused by an expected cause or do I need to write also some kind of parser? That would be IMHO a huge overhead just for a good coverage.
For those of you which need a example:
#SupportedAnnotationTypes("eu.rekisoft.java.preprocessor.Decorator")
#SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ExamplePreprocessor extends AbstractProcessor {
public ExamplePreprocessor() {
super();
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for(Element elem : roundEnv.getElementsAnnotatedWith(Decorator.class)) {
Method method = Method.from((ExecutableElement)elem);
if(!method.matchesTypes(String.class, StringBuilder.class)) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "No! " + elem + " has the wrong args!");
} else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Yey " + elem + " this is fine!");
}
}
return true; // no further processing of this annotation type
}
}
And here is a class which cannot been compiled:
public class AnnotationedExample {
#Decorator
public int breakBuild() {
return -1;
}
#Decorator
public String willCompile(StringBuilder sb) {
return null;
}
}
And finally the boring annotation:
#Retention(RetentionPolicy.SOURCE)
#Target(ElementType.METHOD)
public #interface Decorator {
}
You can also checkout the project at GitHub you just need to check out the project and execute gradlew cJ. You may have to fix the missing x permission on the script on linux and mac.

Google has a very good API for testing compilation. (https://github.com/google/compile-testing). Here's a example to test if a .java file compiles with a processor using junit.
package org.coffeebag.processor;
import com.google.testing.compile.JavaFileObjects;
import com.google.testing.compile.JavaSourceSubjectFactory;
import org.junit.Test;
import java.io.File;
import java.net.MalformedURLException;
import static org.truth0.Truth.ASSERT;
public class ExampleTest {
#Test
public void EmptyClassCompiles() throws MalformedURLException {
final MyProcessor processor = MyProcessor.testMode();
File source = new File("path/to/test/file.java");
ASSERT.about(JavaSourceSubjectFactory.javaSource())
.that(JavaFileObjects.forResource(source.toURI().toURL()))
.processedWith(processor)
.compilesWithoutError();
}
}

This problem has been solved by previous teams writing compilers and annotation processors, so you could re-use their solutions.
The javac team uses a tool called jtreg.
For example, here is a jtreg test that compiles a source file 6 times with different command-line arguments, indicating a different expected compiler output for each one.
The Checker Framework team designed a testing framework specifically for annotation processors. Here is a test case using that framework, where special //:: comments indicate the lines where the annotation processor is expected to issue a warning or an error.

We've written a simplified API that helps access the javax.tools.JavaCompiler API for in-memory Java compilation, which allows for writing simple JUnit based test. In your example, you could write:
#Test
public void testFailAnnotationProcessing() {
ExamplePreprocessor p = new ExamplePreprocessor();
try {
Reflect.compile(
"com.example.test.FailAnnotationProcessing",
"package com.example.test; " +
"public class FailAnnotationProcessing { " +
" #com.example.Decorator " +
" public int breakBuild() { " +
" return -1; " +
" } " +
"}",
new CompileOptions().processors(p)
).create().get();
Assert.fail();
}
catch (ReflectException expected) {}
}
#Test
public void testSucceedAnnotationProcessing() {
ExamplePreprocessor p = new ExamplePreprocessor();
Reflect.compile(
"com.example.test.SucceedAnnotationProcessing",
"package com.example.test; " +
"public class SucceedAnnotationProcessing { " +
" #com.example.Decorator " +
" public String willCompile(StringBuilder sb) { " +
" return null; " +
" } " +
"}",
new CompileOptions().processors(p)
).create().get();
}
More examples can be seen here.

Related

Custom reference contributor is not being called while running tests for Intellij Platform plugin

I'm developing a plugin for Intellij Platform (actually for PhpStorm).
My plugin is providing references within string literal expressions in PHP language.
I created a reference contributor and a few reference provider classes.
My plugin works perfectly when I run it within PhpStorm.
My plugin becomes bigger, so I decided to write a few tests for it.
I created an unit test using IntelliJ IDEA "Create test" action and choosed JUnit 3.
When I run test (see code below), the class MyPsiReferenceContributor is not being accessed at all, and test expectedly fails ("Test failed").
parent.getReferences() returns an empty array (parent is an instance of StringLiteralExpressionImpl).
Is seems that MyPsiReferenceContributor is not being registered in test environment.
What should I do to make my reference contributor available within tests?
I used this project https://github.com/Haehnchen/idea-php-symfony2-plugin as example,
but my plugin is not using Gradle as theirs does.
This is how I registered my reference contributor in plugin.xml:
<extensions defaultExtensionNs="com.intellij">
<psi.referenceContributor implementation="com.zenden2k.MyFrameworkIntegration.MyPsiReferenceContributor"/>
</extensions>
This is my reference contributor:
public class MyPsiReferenceContributor extends PsiReferenceContributor {
// This is not being called while running tests! :(
#Override
public void registerReferenceProviders(PsiReferenceRegistrar registrar) {
MyPsiReferenceProvider provider = new MyPsiReferenceProvider();
registrar.registerReferenceProvider(StandardPatterns.instanceOf(StringLiteralExpression.class), provider);
}
}
This is my test case:
package com.zenden2k.MyFrameworkIntegration.tests;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.testFramework.fixtures.BasePlatformTestCase;
public class MyPsiReferenceContributorTest extends BasePlatformTestCase {
public void setUp() throws Exception {
super.setUp();
myFixture.copyFileToProject("GetStaticDatasourceFixture.php");
myFixture.copyFileToProject("user.xml");
}
protected String getTestDataPath() {
return "src/test/com/zenden2k/MyFrameworkIntegration/tests/fixtures";
}
public void testThatStaticDatasourceReferenceIsProvided() {
final ElementPattern<?> pattern = PlatformPatterns.psiElement().withName("test");
myFixture.configureByText("user.php",
"<?php \n" +
"class MyClass extends CXMLObject {\n" +
" public function test() {\n" +
" $this->getStaticDatasource('t<caret>est');\n" +
" }\n" +
"}\n"
);
PsiElement psiElement = myFixture.getFile().findElementAt(myFixture.getCaretOffset());
if (psiElement == null) {
fail("Fail to find element in caret");
}
PsiElement parent = psiElement.getParent();
final PsiReference[] ref = parent.getReferences();
for (PsiReference reference : ref) {
PsiElement element = reference.resolve();
if (pattern.accepts(element)) {
return;
}
}
fail("Test failed");
}
}
First of all - using Gradle is a recommended way for the development of the plugin. You may check the Building Plugins with Gradle docs section for more details.
There is a Simple Language Sample project available in the IntelliJ Platform SDK Code Samples repository that covers testing of the PSI References.

Strange issue with using Java reflection across multiple projects

I have created a Java project that implements a State Machine. The idea behind it is that the SM itself runs in a background thread as a simple loop over an array of objects representing different actions.
The actions in question are from another object called an ActionPack that contains the Action objects.
This works well using Java reflection to look into the instantiated ActionPack at run time and import them into the state machine where they are pushed onto the array. The state machine then iterates over the array when told to run and calls the execute method of each action.
As I said, this works well and all unit tests are green ... but only when I run the them from within IntelliJ. If I try running them via "mvn test", the Reflection code fails to find the classes within the action pack.
This is the failing reflection method:
/**
* Use introspection to read in all of the classes in a given action pack
* Filter out the ones that are actions and return them in an array
*/
public ArrayList getActionsFromActionPack(
DataAccessLayer dataAccessLayer,
String runRoot
) {
String packageName = this.getClass().getPackage().getName();
System.out.println("PACKAGE NAME = " + packageName);
List<ClassLoader> classLoadersList = new LinkedList<>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
.setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
.filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix(packageName))));
Set<Class<? extends Action>> classes = reflections.getSubTypesOf(Action.class);
System.out.println("Number of classes = " + classes.size());
ArrayList<IAction> actions = new ArrayList<>();
for (Class a : classes) {
try {
Action action = (Action) Class.forName(a.getName()).newInstance();
// Give the action access to the DAL and path to control directory
action.setDataAccessLayer(dataAccessLayer);
action.setRunRoot(runRoot);
actions.add(action);
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.err.println(e.getClass().getName() + ": " + e.getMessage());
System.exit(1);
} catch (IllegalAccessException e) {
e.printStackTrace();
System.err.println(e.getClass().getName() + ": " + e.getMessage());
System.exit(1);
} catch (InstantiationException e) {
e.printStackTrace();
System.err.println(e.getClass().getName() + ": " + e.getMessage());
System.exit(1);
}
}
return actions;
}
With those debug print statements in there, when I run the Unit Tests from within IntelliJ I see this output:
PACKAGE NAME = com.github.museadmin.infinite_state_machine.core.action_pack
Number of classes = 7
PACKAGE NAME = com.github.museadmin.infinite_state_machine.core.action_pack
Number of classes = 7
PACKAGE NAME = com.github.museadmin.infinite_state_machine.core.action_pack
Number of classes = 7
PACKAGE NAME = com.github.museadmin.infinite_state_machine.test.classes
Number of classes = 1
PACKAGE NAME = com.github.museadmin.infinite_state_machine.core.action_pack
Number of classes = 7
PACKAGE NAME = com.github.museadmin.infinite_state_machine.core.action_pack
Number of classes = 7
Process finished with exit code 0
But when I run via mvn test:
PACKAGE NAME = com.github.museadmin.infinite_state_machine.core.action_pack
Number of classes = 0
Exception in thread "UnitTestThread" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.get(ArrayList.java:429)
at com.github.museadmin.infinite_state_machine.core.InfiniteStateMachine.run(InfiniteStateMachine.java:36)
at java.lang.Thread.run(Thread.java:745)
So it looks like the reflective method correctly identifies the package that we're in, but then fails to find the classes within it if called via maven.
The failure occurs in the final line of setup() of the unit test when I call importActionPack
#Before
public void setup() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL is = loader.getResource(PROPERTIES);
ismCoreActionPack = new ISMCoreActionPack();
infiniteStateMachine = new InfiniteStateMachine(is.getPath());
infiniteStateMachine.importActionPack(ismCoreActionPack);
}
This code is spread across three projects:
infinite-state-machine (The state machine itself)
infinite-state-machine-common (Where the action pack and action types are defined)
infinite-state-machine-core (The action pack I'm failing to import)
The Action Pack has an interface in infinite-state-machine-common:
package com.github.museadmin.infinite_state_machine.common.action;
import com.github.museadmin.infinite_state_machine.common.dal.DataAccessLayer;
import org.json.JSONObject;
import java.util.ArrayList;
public interface IActionPack {
JSONObject getJsonObjectFromResourceFile(String fileName);
ArrayList getActionsFromActionPack(
DataAccessLayer dataAccessLayer,
String runRoot
);
}
The actual implementation of the action pack is also in infinite-state-machine-common:
public class ActionPack implements IActionPack
And that is where the reflections method public ArrayList getActionsFromActionPack() is found.
The Action class and its interface are also in infinite-state-machine-common:
package com.github.museadmin.infinite_state_machine.common.action;
import com.github.museadmin.infinite_state_machine.common.dal.DataAccessLayer;
import org.json.JSONObject;
import java.util.ArrayList;
public interface IAction {
void execute();
Boolean active();
boolean active(String action);
void activate(String actionName);
Boolean afterActionsComplete();
Boolean beforeActionsComplete();
void clearPayload(String actionName);
String createRunDirectory(String directory);
void deactivate();
void deactivate(String actionFlag);
JSONObject getJsonObjectFromFile(String fileName);
ArrayList<JSONObject> getUnprocessedMessages();
void insertMessage(JSONObject message);
void insertProperty(String property, String value);
void markMessageProcessed(Integer id);
void setDataAccessLayer(DataAccessLayer dataAccessLayer);
void setRunRoot(String runRoot);
String queryProperty(String property);
String queryRunPhase();
void setState(String stateName);
void updatePayload(String actionName, String payload);
void updateProperty(String property, String value);
void updateRunPhase(String runPhase);
void unsetState(String stateName);
}
The Action parent class itself is too big to post here but is defined in the same package:
public abstract class Action implements IAction {
}
If it helps, this is a typical action from the core package showing the execute method:
package com.github.museadmin.infinite_state_machine.core.action_pack;
import com.github.museadmin.infinite_state_machine.common.action.Action;
/**
* Action that verifies the state of the ISM and confirms
* we have booted up correctly and can now run
*/
public class ActionConfirmReadyToRun extends Action {
/**
* Confirm we've got through the bootstrapping process ok.
* and all BEFORE actions completed
*/
public void execute() {
if (active()) {
if (beforeActionsComplete()) {
setState("READY_TO_RUN");
updateRunPhase("RUNNING");
deactivate();
}
}
}
}
SUMMARY
I don't know if this is a problem with the way I am using the Reflections library as I would expect it to fail when I ran the tests from IntelliJ...
So perhaps this is an issue with Maven?
I have built and locally installed all of the projects, so they are definitely available and built at this point.
FYI - At the moment I have put the Unit tests in their own separate project to prevent this issue from breaking the build as I push the projects to Nexus.
Any help or guidance on debugging this issue would be much appreciated.
Brad

How to get the exception that was thrown when a Cucumber test failed in Java?

I can perform actions on test failure by using:
#After
public void afterTest(Scenario scenario) {
if (scenario.isFailed()) {
/*Do stuff*/
}
}
However some of the actions I need to perform depend on the Exception that was thrown and in what context it was thrown. Is there a way to get the Throwable that caused the test to fail? For example in JUnit I would do this by extending TestWatcher and add a rule to my tests:
#Override
protected void failed(Throwable e, Description description) {
/*Do stuff with e*/
}
However the cucumber-junit iplementation does not allow the use of rules, so this solution would not work with Cucumber.
I don't think I need to explain why accessing a thrown exception on test failure would be useful, however I will still provide an Example:
My test environment is not always stable, so my tests might fail unexpectedly at any moment (there's no specific place I can try to catch the exception since it could occur at any time). When this happens I need the test to reschedule for another attempt, and log the incident so that we can get some good statistical data on the environment instability (when, how frequent, how long etc.)
The problem with the work around suggested by Frank Escobar:
By using reflection to reach into a frameworks internals you're depending on implementation details. This is a bad practice, when ever the framework changes its implementation your code may break as you will observe in Cucumber v5.0.0.
Hooks in Cucumber are designed to manipulate the test execution context before and after a scenario. They're not made to report on the test execution itself. Reporting is cross cutting concern and best managed by using the plugin system.
For example:
package com.example;
import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.Result;
import io.cucumber.plugin.event.Status;
import io.cucumber.plugin.event.TestCase;
import io.cucumber.plugin.event.TestCaseFinished;
public class MyTestListener implements ConcurrentEventListener {
#Override
public void setEventPublisher(EventPublisher publisher) {
publisher.registerHandlerFor(TestCaseFinished.class, this::handleTestCaseFinished);
}
private void handleTestCaseFinished(TestCaseFinished event) {
TestCase testCase = event.getTestCase();
Result result = event.getResult();
Status status = result.getStatus();
Throwable error = result.getError();
String scenarioName = testCase.getName();
String id = "" + testCase.getUri() + testCase.getLine();
System.out.println("Testcase " + id + " - " + status.name());
}
}
When using JUnit 4 and TestNG you can activate this plugin using:
#CucumberOptions(plugin="com.example.MyTestListener")
With JUnit 5 you add it to junit-platform.properties:
cucumber.plugin=com.example.MyTestListener
Or if you are using the CLI
--plugin com.example.MyTestListener
I've implemented this method using reflections. You can't access directly to steps errors (stack trace). I've created this static method which allows you to access to "stepResults" attribute and then you can iterate and get the error and do whatever you want.
import cucumber.runtime.ScenarioImpl;
import gherkin.formatter.model.Result;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
#After
public void afterScenario(Scenario scenario) {
if (scenario.isFailed())
logError(scenario);
}
private static void logError(Scenario scenario) {
Field field = FieldUtils.getField(((ScenarioImpl) scenario).getClass(), "stepResults", true);
field.setAccessible(true);
try {
ArrayList<Result> results = (ArrayList<Result>) field.get(scenario);
for (Result result : results) {
if (result.getError() != null)
LOGGER.error("Error Scenario: {}", scenario.getId(), result.getError());
}
} catch (Exception e) {
LOGGER.error("Error while logging error", e);
}
}
You can to this by writing your own custom implementation of Formatter & Reporter interface. The empty implementation of Formatter is the NullFormatter.java which you can extend. You will need to provide implementations for the Reporter interface.
The methods which would be of interest will be the result() of the Reporter interface and possibly the done() method of Formatter. The result() has the Result object which has the exceptions.
You can look at RerunFormatter.java for clarity.
Github Formatter source
public void result(Result result) {
//Code to create logs or store to a database etc...
result.getError();
result.getErrorMessage();
}
You will need to add this class(com.myimpl.CustomFormRep) to the plugin option.
plugin={"pretty", "html:report", "json:reports.json","rerun:target/rerun.txt",com.myimpl.CustomFormRep}
More details on custom formatters.
You can use the rerun plugin to get a list of failed scenarios to run again. Not sure about scheduling a run of failed tests, code to create a batch job or schedule one on your CI tool.
This is the workaround for cucumber-java version 4.8.0 using reflection.
import cucumber.api.Result;
import io.cucumber.core.api.Scenario;
import io.cucumber.core.logging.Logger;
import io.cucumber.core.logging.LoggerFactory;
import io.cucumber.java.After;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
#After
public void afterScenario(Scenario scenario) throws IOException {
if(!scenario.getStatus().isOk(true)){
logError(scenario);
}
}
private static void logError(Scenario scenario) {
try {
Class clasz = ClassUtils.getClass("cucumber.runtime.java.JavaHookDefinition$ScenarioAdaptor");
Field fieldScenario = FieldUtils.getField(clasz, "scenario", true);
fieldScenario.setAccessible(true);
Object objectScenario = fieldScenario.get(scenario);
Field fieldStepResults = objectScenario.getClass().getDeclaredField("stepResults");
fieldStepResults.setAccessible(true);
ArrayList<Result> results = (ArrayList<Result>) fieldStepResults.get(objectScenario);
for (Result result : results) {
if (result.getError() != null) {
LOGGER.error(String.format("Error Scenario: %s", scenario.getId()), result.getError());
}
}
} catch (Exception e) {
LOGGER.error("Error while logging error", e);
}
}
For cucumber-js https://www.npmjs.com/package/cucumber/v/6.0.3
import { After } from 'cucumber'
After(async function(scenario: any) {
const exception = scenario.result.exception
if (exception) {
this.logger.log({ level: 'error', message: '-----------StackTrace-----------' })
this.logger.log({ level: 'error', message: exception.stack })
this.logger.log({ level: 'error', message: '-----------End-StackTrace-----------' })
}
})
After a lot of experimentation I now removed the Before/After-Annotations and rely on Cucumber-Events instead. They contain the TestCase (which is what the Scenario-class wraps) and a Result where you can call getError(); to get the Throwable.
Here is a simple example to get it working
import io.cucumber.plugin.EventListener;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.Result;
import io.cucumber.plugin.event.Status;
import io.cucumber.plugin.event.TestCase;
import io.cucumber.plugin.event.TestCaseFinished;
import io.cucumber.plugin.event.TestCaseStarted;
import org.openqa.selenium.WebDriver;
public class TestCaseListener implements EventListener {
#Override
public void setEventPublisher(final EventPublisher publisher) {
publisher.registerHandlerFor(TestCaseStarted.class, this::onTestCaseStarted);
publisher.registerHandlerFor(TestCaseFinished.class, this::onTestCaseFinished);
}
public void onTestCaseStarted(TestCaseStarted event) {
TestCase testCase = event.getTestCase();
System.out.println("Starting " + testCase.getName());
// Other stuff you did in your #Before-Method.
// ...
}
private void onTestCaseFinished(final TestCaseFinished event) {
TestCase testCase = event.getTestCase();
System.out.println("Finished " + testCase.getName());
Result result = event.getResult();
if (result.getStatus() == Status.FAILED) {
final Throwable error = result.getError();
error.printStackTrace();
}
// Other stuff you did in your #After-Method.
// ...
}
}
All that's left to do is to register this class as a Cucumber-Plugin.
I did this by modifying my #CucumberOptions-annotation:
#CucumberOptions(plugin = {"com.example.TestCaseListener"})
I find this much cleaner than all of this reflection-madness, however it requires a lot more code-changes.
Edit
I don't know why, but this caused a lot of tests to randomly fail in a multithreaded environment.
I tried to figure it out, but now also use the ugly reflections mentioned in this thread:
public class SeleniumUtils {
private static final Logger log = LoggerFactory.getLogger(SeleniumUtils.class);
private static final Field field = FieldUtils.getField(Scenario.class, "delegate", true);
private static Method getError;
public static Throwable getError(Scenario scenario) {
try {
final TestCaseState testCase = (TestCaseState) field.get(scenario);
if (getError == null) {
getError = MethodUtils.getMatchingMethod(testCase.getClass(), "getError");
getError.setAccessible(true);
}
return (Throwable) getError.invoke(testCase);
} catch (Exception e) {
log.warn("error receiving exception", e);
}
return null;
}
}
If you just want to massage the result being sent to the report then you can extend the CucumberJSONFormatter and override the result method like this:
public class CustomReporter extends CucumberJSONFormatter {
CustomReporter(Appendable out) {
super(out);
}
/**
* Truncate the error in the output to the testresult.json file.
* #param result the error result
*/
#Override
void result(Result result) {
String errorMessage = null;
if (result.error) {
errorMessage = "Error: " + truncateError(result.error);
}
Result myResult = new Result(result.status, result.duration, errorMessage);
// Log the truncated error to the JSON report
super.result(myResult);
}
}
Then set the plugin option to:
plugin = ["com.myimpl.CustomReporter:build/reports/json/testresult.json"]

java.lang.RuntimeException: Stub! in Android with Mockito

I have been looking on Google for this error, and most of the answers I have found is to move junit dependence, to the top. Or use another mocking framework. I already moved the dependence and still fails, and the whole project uses mockito and powermock.
This is in resume, the code.
package co.pack.session;
import com.google.gson.JsonObject;
import org.junit.Test;
import co.pack.Session.Organization;
import static org.junit.Assert.assertEquals;
public class TestOrganization {
#Test
public void testLoadJson() {
JsonObject json = new JsonObject();
json.addProperty("theme_color", "red");
Organization organization = new Organization();
organization.loadFromJson(json);
assertEquals("red", Organization.getThemeColor());
}
}
Implementation
public static void loadFromJson(JsonObject json) {
Organization.name = json.has("name") ? json.get("name").getAsString() : "";
Organization.image = json.has("image") ? json.get("image").getAsString() : "";
printActualOrganization();
}
private static void printActualOrganization() {
Log.i(TAG, "_name_ " + name);
Log.i(TAG, "_image_ " + image);
}
It fails on a Log line
Log.i(TAG, "_name_ " + name);
And got this
java.lang.RuntimeException: Stub!
at android.util.Log.i(Log.java:9)
at co.mobico.Session.Organization.loadJson(Organization.java:50)
at co.mobico.session.TestOrganization.testLoadJson(TestOrganization.java:28)
Log lines, never causes any error on my test, I don't know what is happening in this case.
You can try add the following options to your app build.gradle.
android {
testOptions {
unitTests.returnDefaultValues = true
}
}
This should prevent you from getting this RuntimeException, because in this case the Android-Methods will return default values. Be aware that this might raise other problems, e.g. when using TextUtils.equals(...).
Basically I agree with the strategy to mock every dependency of your unit under test, but you can use the return values as a kind of workaround.
The problem is that you call a method directly on a mock.
You usually don't do that.
Usually you create a normal instance of your class under test (cut) and mocks for the dependencies it communicates with:
#Test public void exampleWithMocks(){
// arrange
DataTransferObject dto = new DataTransferObject();
dto.setSomeProperty(SOME_PRIMITIVE_VALUE_OR_STRING);
SomeServiceTheCutDependsOn dependency = mock( SomeServiceTheCutDependsOn.class);
ClassUnderTest cut = new ClassUnderTest(dependency);
// act
Object result = cut.doSomethingWith(dto);
// assert
assertThat(result,notNullValue());
verify(dependency).expectedMethodCall(dto);
}

Override Thread.sleep()

We have few classes which extends a base class. We noticed that we use quit a few sleeps method and we wanted to log when a sleep occurs. Is there a way to override the Thread.sleep method in which I can add some custom logic ( ie logging) and then just call the actual Thread.sleep()? This way I wouldn't have to change all the places where Thread.sleep is being used in my bases classes. I'm open to other options as well.
You cannot override Thread.sleep method, you cannot instrument or transform it either as it's a native method. One way is to automatically add logging to all places which call Thread.sleep using a Java Agent.
While the following approach works and is quite fun, in your case it's probably much better to refactor all calls to the Thread.sleep into a separate method and add the logging there.
You can find an introduction to Java Agents here. In a nutshell it's a special mechanism which allows (among other) transformation of loaded Java byte code. The following example of an Java Agent class automatically enhances all calls to the Thread.sleep with System.out logging and measure time spent in the method:
package fi.schafer.agent;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class LoggingAgent {
public static void premain(String agentArgument, Instrumentation instrumentation) throws Exception {
instrumentation.addTransformer(new ClassFileTransformer() {
#Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
return doClass(className, classBeingRedefined, classfileBuffer);
}
});
}
/**
* Method enhances calls to Thread.sleep with logging.
*/
private static byte[] doClass(String name, Class clazz, byte[] b) {
ClassPool pool = ClassPool.getDefault();
CtClass cl = null;
try {
cl = pool.makeClass(new java.io.ByteArrayInputStream(b));
final CtMethod[] targetMethods = cl.getDeclaredMethods();
for (CtMethod targetMethod : targetMethods) {
targetMethod.instrument(new ExprEditor() {
public void edit(final MethodCall m) throws CannotCompileException {
if ("java.lang.Thread".equals(m.getClassName()) && "sleep".equals(m.getMethodName())) {
m.replace("{long startMs = System.currentTimeMillis(); " +
"$_ = $proceed($$); " +
"long endMs = System.currentTimeMillis();" +
"System.out.println(\"Logging Thread.sleep call execution, ms: \" + (endMs-startMs));}");
}
}
});
return cl.toBytecode();
}
} catch (Exception e) {
System.err.println("Could not instrument " + name
+ ", exception : " + e.getMessage());
} finally {
if (cl != null) {
cl.detach();
}
}
return b;
}
}
You will need to compile it into a loggerAgent.jar file and include the following META-INF/MANIFEST.MF in it:
Manifest-Version: 1.0
Premain-Class: fi.schafer.agent.LoggingAgent
Boot-Class-Path: javassist.jar
Download JavaAssist and put it into same folder as your jar with compiled Agent. Run your application with parameter -javaagent:loggerAgent.jar.
You can download a full example. Just extract it, open folder release and run the application with java -cp loggerAgent.jar -javaagent:loggerAgent.jar Test
More information and more examples can be found in this excellent article.

Categories