Automated test generation can significantly reduce the time and effort required to write and maintain a comprehensive test suite. Diffblue Cover takes this process further by automatically using reinforcement learning to create unit tests for Java applications.
While this can be incredibly powerful, you may want more control over the generated tests. That’s where Cover Annotations come into play.
Cover Annotations provide a way to influence Diffblue Cover’s test generation process from within your codebase. Instead of relying purely on the automated analysis, you can add annotations that guide how tests should be written – whether that’s mocking a specific dependency, using particular input values, or highlighting certain factory methods as preferred constructors for test objects.
With these annotations, you can instruct the AI to consider your domain-specific insight, resulting in tests that are more aligned with your needs.
In this article, we’ll explore how to set up Cover Annotations and how to use them to adjust Diffblue Cover’s test generation strategy. We’ll walk through various types of annotations – mocking annotations, custom input annotations, and interesting value annotations – to help you understand and apply them effectively in your own projects.
Why Cover Annotations Are Useful
Diffblue Cover automatically explores code paths and generates meaningful tests. However, no automation tool can fully understand the nuances of every codebase by default.
Your application may have:
- Complex objects that need specific values to trigger certain logic.
- Methods that would be better tested with a known set of input data.
- Classes should be mocked to isolate the logic under test, but Diffblue Cover might not recognize that by itself.
- Internal factory methods would make tests more straightforward and more meaningful if only the test generator knew how to use them.
Cover Annotations let you “teach” the test generator about these nuances. You can fine-tune how tests are produced—resulting in higher-quality tests that save even more developer time.
Setting Up Cover Annotations
Cover Annotations are packaged as a simple dependency available from Maven Central.
They are licensed under the Apache License 2.0, and you can add them to your project with just a few lines in your pom.xml
or Gradle build.gradle
files.
Maven Example
To use Cover Annotations in a Maven project, add the following dependency to your pom.xml:
<dependencies>
<dependency>
<groupId>com.diffblue.cover</groupId>
<artifactId>cover-annotations</artifactId>
<version>1.2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
Gradle Example
For a Gradle project, add the dependency to your build.gradle:
dependencies {
compileOnly 'com.diffblue.cover:cover-annotations:1.2.0'
}
You can find the source code for Cover Annotations on GitHub and fetch the latest version on Maven Central.
Mocking Annotations: Controlling Test Dependencies
One of the most powerful features of Cover Annotations is the ability to control how Diffblue Cover handles mocking in your tests. Let’s look at a practical example:
public class PaymentProcessor {
@InTestsMock(PaymentGateway.class)
public PaymentResult processPayment(PaymentGateway gateway, double amount) {
try {
return gateway.charge(amount);
} catch (GatewayException e) {
return PaymentResult.failed(e.getMessage());
}
}
}
In this example, we’ve annotated the processPayment
method to indicate that PaymentGateway
should be mocked in tests. This is particularly useful when dealing with external services or complex dependencies.
You can also prevent Diffblue Cover from mocking a dependency when needed:
public class DataValidator {
@InTestsMock(value = StringUtils.class, decision = MockDecision.FORBIDDEN)
public boolean isValidData(String input) {
return StringUtils.isNotBlank(input) && input.length() <= 100; }
}
The decision configuration MockDecision.FORBIDDEN
tells Diffblue Cover to back off from mocking the StringUtils
class and use the actual implementation.
Custom Input Annotations: Specifying Test Data
Sometimes, you want to ensure specific test inputs are used.
Custom input annotations let you influence which inputs Diffblue Cover uses during test generation. By providing specific values, you ensure tests cover particular edge cases or highlight important scenarios.
Use custom input annotations when Diffblue Cover struggles to find the correct input values to achieve complete code coverage or when you want tests to be more meaningful by using domain-specific data (e.g., a particular username, a special string token, or a boundary integer value).
Cover Annotations provides a range of annotations for different data types. Here’s a practical example using string inputs:
public class EmailValidator {
@InTestsUseStrings({
"[email protected]",
"invalid-email",
""
})
public boolean isValidEmail(String email) {
return
email != null &&
email.contains("@") &&
email.matches("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}");
}
}
For numeric values, you can use type-specific annotations:
public class AgeCalculator {
@InTestsUseIntegers({18, 65, 0, -1})
public String getAgeCategory(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
if (age < 18) {
return "Minor";
}
if (age >= 65) {
return "Senior";
}
return "Adult";
}
}
Similarly, you can use other annotations like @InTestsUseIntegers
, @InTestsUseFloats
, or @InTestsUseEnums
to steer test inputs for various data types. You can find all input annotations here.
Interesting Value Annotations: Highlighting Factory Methods
Sometimes, you need to show Diffblue Cover which methods can produce meaningful instances of complex types. Interesting value annotations, such as @InterestingTestFactory
, let you promote specific methods as a candidate for test data generation.
This is particularly useful for more complex classes that don’t have obvious public constructors or rely on factories to produce instances.
For example, if your code depends on a User object that can only be constructed through a private constructor and a specific factory method, you can mark that factory method to guide the test generation.
class User {
private final String id;
private final boolean active;
private User(String id, boolean active) {
this.id = id;
this.active = active;
}
@InterestingTestFactory
public static User createTestUser(String id, boolean active) {
return new User(id, active);
}
public boolean isActive() {
return active;
}
}
In this example, we’ve marked createTestUser
as an interesting factory method, helping Diffblue Cover understand how to create User instances for tests.
Understanding the Annotation Scope
Cover Annotations use a hierarchical system that determines how annotations apply across different levels of your codebase. Think of it like inheritance in object-oriented programming, where more specific declarations override more general ones. Let’s explore how this works in practice.
The Hierarchy of Annotation Scopes
Cover Annotations can be applied at three different levels in your code:
- Package Level: These annotations are the most broad and affect all classes and methods within that package. You apply them using
package-info.java
:
// In src/main/java/com/example/payment/package-info.java
@InTestsMock(PaymentGateway.class)
package com.example.payment;
- Class Level: These annotations apply to all methods within a class, overriding any package-level annotations:
@InTestsMock(PaymentGateway.class)
public class PaymentProcessor {
public PaymentResult processPayment(PaymentGateway gateway, double amount) throws GatewayException {
// This method will use mocked PaymentGateway because of class-level annotation
return gateway.charge(amount);
}
public boolean validatePayment(PaymentGateway gateway) {
// This method will also use a mocked PaymentGateway
return gateway.isAvailable();
}
}
- Method Level: These are the most specific annotations and override both package and class-level annotations:
public class PaymentProcessor {
// This method overrides any class or package-level annotations
@InTestsMock(PaymentGateway.class)
public boolean validatePayment(PaymentGateway gateway) {
// This method will use mocked PaymentGateway
return gateway.isAvailable();
}
}
This hierarchical system allows you to:
- Set default behaviors at the package level
- Override those defaults for specific classes when needed
- Fine-tune individual methods without affecting the rest of the class
When working with multiple annotations, remember that more specific scopes (method > class > package) always take precedence, allowing you to gradually refine how Diffblue Cover generates tests for different parts of your codebase.
Best Practices for Using Cover Annotations
- Use annotations sparingly – only when you need to override Diffblue Cover’s default behavior
- Keep test inputs realistic and meaningful for your domain
- Document why you’re using specific annotations, especially when dealing with complex scenarios
- Review generated tests to ensure the annotations are having the desired effect
- Remember that command-line options take precedence over annotations
Conclusion
Diffblue Cover is a powerful tool for automating unit test generation. Cover Annotations let you guide the tool’s decision-making process: you can specify when and how to mock, which inputs to use, and which methods are ideal test data factories.
This level of customization can help you achieve better coverage, more relevant test scenarios, and, ultimately, a more valuable test suite.
Cover Annotations have you covered whether you’re dealing with tricky dependencies, input corner cases, or complex construction patterns.
Start incorporating Cover Annotations into your workflow. Give Diffblue Cover the hints it needs, and watch your test generation process become even more effective and aligned with your project’s goals and domain.
Try Diffblue Cover now
Start your free trial of Diffblue Cover and see the difference annotations can make to your test suite.