Creating Custom Java Annotations for Your Tests

Hello, today I am going to tell you to create a new annotation for your development and test project. In this way, you will use your own custom annotations in your JAVA projects.

What are annotations?

An annotation is a form of syntactic metadata that can be added to any Java code. Classes, methods, variables, parameters might be annotated. Annotations give specific purposes to its annotated field.

Why do we use annotations?

In a test project, we use many annotations like @Test, @Before, @DataProvider etc. In this way, our compiled class run those methods according to their structure. Think about TestNG, if you have a DataProvider annotation and you related a Test with it, first DataProvider then its test will be executed. Those annotations increase our productivity in testing project.

Then why do we need custom annotations?

There is no specific answer to that question. It depends on your need. In my project, I needed to separate my test into modules and prioritize test cases according to our priority category. Then in the reporting, I have to summarise my execution report in a custom format.

Example:

High Priority Payment Failed TC Count: 5

Low Priority Login Failed TC Count: 2

So let’s take a look at how we achieve this?

Technology Stack and Libraries Used for this Example:

  • Maven
  • Java 8
  • TestNG
  • ExtentReport

Creating the Annotation

Annotation is separated into two parts.

Retention Type

  • CLASS Annotations are to be recorded in the class file by the compiler but need not be retained by the VM at runtime.
  • RUNTIME Annotations are to be recorded in the class file by the compiler and retained by the VM at runtime, so they may be read reflectively.
  • SOURCE Annotations are to be discarded by the compiler.

We mostly use RUNTIME as we’ll analyze our annotation during code execution.

Target

  • ANNOTATION_TYPE Annotation type declaration
  • CONSTRUCTOR Constructor declaration
  • FIELD Field declaration (includes enum constants)
  • LOCAL_VARIABLE Local variable declaration
  • METHOD Method declaration
  • PACKAGE Package declaration
  • PARAMETER Parameter declaration
  • TYPE Class, interface (including annotation type), or enum declaration

Attention: You cannot use a FIELD annotation in your constructor or in a method. You should think about your annotations usage carefully before implementing it.

Then you have variables of an annotation. You can have a different type of variable in your Annotation like String, int, Enums, String Arrays, etc..

Sample Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Career {
       String[] name() default "Canberk";
       int age() default 33;
       String[] workedCompanies() default "none";
}

Sample Usage

@Career(name="Canberk",age=33,workedCompanies = {"Keytorc","Testinium","Qardio B.V. Netherlands"})
public class SampleClass {
}

In the sample project, I have one Method and one Class based Annotations. You can check their structure. I also added an Enum Type variable in one of them. Class-based annotation defines the module of my project. Method based annotation defines the priority of the test cases.

Here is a sample from my tests:

@Module(module = "payment")
public class PaymentTests {

@Test
@TestInfo(risk=TestInfo.Risk.HIGH)
public void visaPayment(){
       Assert.assertTrue(true);
   }
}

And we are done with creating our annotations. Then what? We need to do something with those annotations so that we can get feedback about our failing test cases by module and priority.

Create a TestNG Listener

In order to create and have a deep understanding of TestNG listener, please refer to this article

I am only going to implement the onTestFailure method as I want to count the failed test cases numbers by priority.

You can get your method’s annotation by using this snippet:

Annotation annotation = iTestResult.getMethod().getConstructorOrMethod().getMethod().getAnnotation(SAMPLE.class)

Then you can transform this generic Annotation object to any of your custom annotations by simply casting it as below.

TestInfo testInfo = (TestInfo) annotation;

Finally, you can get its field by simply calling them as below:

risk = testInfo.risk();

Then we implement our custom reporting algorithm. I don’t put all my code as it’s confidential :)

@Override
    public void onTestFailure(ITestResult iTestResult) {
        Annotation testInfoAnnotation = getAnnotation(iTestResult,TestInfo.class);
        TestInfo.Risk risk;
        if (testInfoAnnotation != null) {
            TestInfo testInfo = (TestInfo) testInfoAnnotation;
            risk = testInfo.risk();
            if(risk == TestInfo.Risk.HIGH)
                countHigh++;
            else if(risk == TestInfo.Risk.MEDIUM)
                countMedium++;
            else if(risk == TestInfo.Risk.LOW)
                countLow++;
        }
    }

Right now, you are able to create an annotation, read its value whenever its necessary and do some stuff. Happy implementing into your projects!

You can check out the source code and get a better understand of the sample.

GitHub Project Link: https://github.com/swtestacademy/java_annotation_example

Thanks.
Canberk Akduygu

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.