| name | create-codeql-query-unit-test-java |
|---|---|
| description | Create comprehensive unit tests for CodeQL queries targeting Java code. Use this skill when you need to create, validate, or enhance test coverage for Java CodeQL queries using the CodeQL Development MCP Server tools. |
This skill guides you through creating comprehensive unit tests for CodeQL queries that analyze Java code.
- Creating new unit tests for a Java CodeQL query
- Adding test cases to existing Java query tests
- Validating Java query behavior against known code patterns
- Testing Java security queries, code quality queries, or analysis queries
Before creating tests, ensure you have:
- A CodeQL query (
.qlfile) that you want to test - Understanding of what Java code patterns the query should detect
- Knowledge of Java language features and frameworks relevant to your query
- Access to CodeQL Development MCP Server tools
- A query pack directory where your query is organized
Organize tests in your query pack's test directory:
<query-pack>/test/{QueryName}/
├── {QueryName}.qlref # Reference to the query being tested
├── Example1.java # Primary test source file
├── Example2.java # Additional test cases (optional)
├── Helper.java # Helper classes if needed (optional)
├── {QueryName}.expected # Expected query results
└── {QueryName}.testproj/ # Generated test database (auto-created)
- Test source files: Use
Example1.java,Example2.java, etc. orTest.java - Helper files: Use
.javaextension (e.g.,Helper.java,TestData.java) - Query reference:
{QueryName}.qlref(exact match to query directory name) - Expected results:
{QueryName}.expected(exact match to query name)
Create the test directory structure within your query pack:
mkdir -p <query-pack>/test/{QueryName}Create {QueryName}.qlref with the relative path to your query:
src/{QueryName}/{QueryName}.ql
Example (FindInsecureCrypto.qlref):
src/FindInsecureCrypto/FindInsecureCrypto.ql
Important: The path is relative to your query pack's source directory (where your queries are organized).
Create Example1.java with comprehensive test cases covering:
Code patterns that your query should find:
import javax.crypto.Cipher;
import java.security.MessageDigest;
public class InsecureCrypto {
// Positive case: Weak algorithm
public void weakCipher() throws Exception {
Cipher cipher = Cipher.getInstance("DES"); // Should be detected: weak algorithm
// Use cipher...
}
// Positive case: Weak hash
public void weakHash() throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5"); // Should be detected: weak hash
// Use digest...
}
}Code patterns that your query should not flag:
import javax.crypto.Cipher;
import java.security.MessageDigest;
public class SecureCrypto {
// Negative case: Strong algorithm
public void strongCipher() throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); // Should NOT be detected: strong algorithm
// Use cipher...
}
// Negative case: Strong hash
public void strongHash() throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256"); // Should NOT be detected: strong hash
// Use digest...
}
}Boundary conditions and unusual scenarios:
// Edge case: Cipher in configuration
public class EdgeCase {
private static final String ALGORITHM = "AES";
public Cipher getCipher() throws Exception {
return Cipher.getInstance(ALGORITHM + "/CBC/PKCS5Padding"); // Test string concatenation
}
}Include relevant Java patterns for your query type:
// Test annotations (Spring, JAX-RS, etc.)
@RestController
public class AnnotatedClass {
@GetMapping("/data")
public String getData(@RequestParam String param) { }
}
// Test lambda expressions and streams
numbers.stream().filter(n -> n > 0).forEach(System.out::println);
// Test try-with-resources
try (FileReader fr = new FileReader("file.txt")) {
// Auto-closeable resources
}Include Comments: Document what each test case validates:
// Test case 1: Detects weak cryptographic algorithms
public void test1() throws Exception {
Cipher cipher = Cipher.getInstance("DES"); // Expected: Alert on this line
}
// Test case 2: Should not flag strong algorithms
public void test2() throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); // Expected: No alert
}Create {QueryName}.expected with the expected query output:
| Example1.java:8:9:8:20 | targetFunc(...) | Call to `targetFunc` from `caller1` |
| Example1.java:12:9:12:20 | targetFunc(...) | Call to `targetFunc` from `caller2` |
Column Definitions:
- First column: File location in format
file:line:col:endLine:endCol - Second column: Code snippet or expression
- Third column: Expected alert message from the query
Important Notes:
- Line and column numbers are 1-indexed (first line is 1, first character is column 1)
- Location format uses colon separators:
file:startLine:startCol:endLine:endCol - Message text should match query output exactly
- Order results by file, then line, then column
Alternative format (if query uses table output):
| file | line | col | endLine | endCol | message |
| Example1.java | 8 | 20 | 8 | 43 | Weak cryptographic algorithm |
| Example1.java | 15 | 25 | 15 | 51 | Use of MD5 hash function |
Use the codeql_test_extract MCP tool to create a test database:
{
"testPath": "<query-pack>/test/{QueryName}",
"searchPath": ["<query-pack>"]
}What This Does:
- Compiles your Java test code
- Creates a CodeQL database at
test/{QueryName}/{QueryName}.testproj/ - Extracts AST and semantic information
- Prepares database for query execution
Java Extraction Notes:
- Supports Java 8, 11, 17, 21 features
- Handles annotations, generics, and lambda expressions
- Processes multiple source files and packages
- Includes standard library modeling for JDK classes
- Extracts framework-specific patterns (Spring, Servlet, etc.)
Before finalizing your query, use PrintAST to understand the Java AST structure:
Use the codeql_query_run MCP tool:
{
"query": "<query-pack>/src/PrintAST/PrintAST.ql",
"database": "<query-pack>/test/{QueryName}/{QueryName}.testproj",
"searchPath": ["<query-pack>"],
"format": "text"
}Use codeql_bqrs_decode to view the AST:
{
"format": "text",
"bqrsFile": "<path-to-results.bqrs>",
"outputPath": "<output-file.txt>"
}Key Java AST Nodes to Look For:
- Methods:
Method,Constructor - Classes:
Class,Interface,EnumType,AnnotationType - Expressions:
MethodCall,VarAccess,FieldAccess,ArrayAccess - Statements:
BlockStmt,IfStmt,ReturnStmt,TryStmt - Types:
TypeAccess,ParameterizedType,ArrayTypeAccess - Annotations:
Annotation,AnnotationElement - Generics:
TypeVariable,WildcardTypeAccess - Lambdas:
LambdaExpr,MethodReference
Execute your tests using the codeql_test_run MCP tool:
{
"testPath": "<query-pack>/test/{QueryName}",
"searchPath": ["<query-pack>"]
}Interpreting Results:
✅ Tests Pass: Output matches .expected file exactly
- All expected alerts are found
- No unexpected alerts are produced
- Line and column numbers match
❌ Tests Fail: Differences between actual and expected
- Missing alerts: Query didn't find expected patterns
- Extra alerts: Query found unexpected patterns
- Position mismatch: Line/column numbers don't match
If tests fail, analyze the differences:
- Review actual query output: Check what the query actually found
- Compare with expected results: Identify discrepancies
- Update query or expected file:
- If query is wrong: Fix the query logic
- If expected is wrong: Update
.expectedfile
- Re-run tests: Use
codeql_test_runagain - Repeat until all tests pass
If the actual results are correct and you want to update the baseline:
{
"testPath": "<query-pack>/test/{QueryName}",
"searchPath": ["<query-pack>"]
}Use codeql_test_accept tool, but only after verifying the results are correct.
Expand test coverage by adding more test files:
- Create
Example2.javawith additional scenarios - Update
{QueryName}.expectedwith new expected results - Re-extract test database with
codeql_test_extract - Run tests again with
codeql_test_run
Include tests for Java features relevant to your query:
- Object-Oriented: Test inheritance, polymorphism, interfaces
- Annotations: Test framework annotations (Spring, JPA, JAX-RS)
- Generics: Test type parameters and wildcards
- Lambda Expressions: Test functional interfaces and method references
- Exception Handling: Test try-catch-finally, try-with-resources
If your query targets specific frameworks, include minimal test cases:
// Spring MVC
@GetMapping("/endpoint")
public String handler(@RequestParam String param) { }
// Servlet API
public void doGet(HttpServletRequest req, HttpServletResponse resp) { }
// JPA
@Entity
public class User {
@Id private Long id;
}For taint tracking queries, test data flow through method calls:
public void dataFlowExample() {
String tainted = getSource(); // Source
processSink(tainted); // Sink
}codeql_test_extract: Extract test databases from Java source codecodeql_test_run: Run query tests and compare with expected resultscodeql_test_accept: Accept actual results as new baseline (use with caution)
codeql_query_compile: Compile CodeQL queries and check for syntax errorscodeql_query_format: Format CodeQL query filescodeql_query_run: Run queries (e.g., PrintAST) against test databases
codeql_bqrs_decode: Decode binary query results to human-readable textcodeql_bqrs_interpret: Interpret results in various formats (SARIF, CSV, graph)codeql_bqrs_info: Get metadata about query results
codeql_pack_install: Install query pack dependencies before testing
❌ Don't:
- Forget to include necessary imports
- Write tests that don't compile
- Use language features not supported by target Java version
- Ignore framework-specific patterns when testing framework queries
- Forget to test annotation-based configurations
- Skip testing generic types and type erasure
- Use internal APIs without proper context
✅ Do:
- Write valid, compilable Java code
- Include comments explaining each test case
- Test both positive and negative cases
- Cover edge cases and boundary conditions
- Use realistic Java patterns from real applications
- Test relevant framework usage (Spring, Jakarta EE, etc.)
- Include annotation and generics tests when relevant
- Test lambda expressions and streams for modern Java queries
Before considering your Java tests complete:
- Test directory created with correct naming
-
.qlreffile correctly references query -
Example1.javaincludes comprehensive test cases - Test code compiles and is valid Java
- All Java features used by query are tested
- Framework-specific patterns tested (if applicable)
- Positive cases (should detect) are included
- Negative cases (should not detect) are included
- Edge cases are covered
-
.expectedfile has correct format with proper locations - Line and column numbers in
.expectedare accurate - Test database extracted successfully with
codeql_test_extract - Tests run successfully with
codeql_test_run - All tests pass (actual matches expected)
- Additional test files added if needed (Example2.java, etc.)
- Tests verified at language level (all Java tests pass)
Detects insecure cryptographic operations in Java code.
server/ql/java/tools/test/FindInsecureCrypto/
├── FindInsecureCrypto.qlref
├── Example1.java
├── FindInsecureCrypto.expected
└── FindInsecureCrypto.testproj/ (auto-generated)
src/FindInsecureCrypto/FindInsecureCrypto.ql
import javax.crypto.*;
import java.security.*;
// Test case 1: Weak cipher algorithm (should detect)
class WeakCipher {
public void useDES() throws Exception {
Cipher cipher = Cipher.getInstance("DES"); // Unsafe: weak algorithm
}
}
// Test case 2: Strong cipher algorithm (should NOT detect)
class StrongCipher {
public void useAES() throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); // Safe: strong algorithm
}
}
// Test case 3: Weak hash function (should detect)
class WeakHash {
public void useMD5() throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5"); // Unsafe: weak hash
}
}
// Test case 4: Strong hash function (should NOT detect)
class StrongHash {
public void useSHA256() throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256"); // Safe: strong hash
}
}
// Test case 5: Static IV usage (should detect - edge case)
class StaticIV {
public void staticInitVector() throws Exception {
byte[] iv = new byte[16]; // Unsafe: static/zero IV
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, generateKey(), new IvParameterSpec(iv));
}
private SecretKey generateKey() throws Exception {
return KeyGenerator.getInstance("AES").generateKey();
}
}| Example1.java:7:24:7:49 | getInstance(...) | Weak cryptographic algorithm: DES |
| Example1.java:21:28:21:56 | getInstance(...) | Weak hash function: MD5 |
| Example1.java:35:20:35:31 | new byte[] | Static initialization vector |
- Verify Java code compiles outside CodeQL
- Check for syntax errors in test files
- Ensure all required imports exist
- Review Java version compatibility
- Check for missing framework dependencies
- Compare actual output with
.expectedfile - Verify line and column numbers are correct (1-indexed)
- Check message text matches exactly
- Review query logic for correctness
- Ensure AST node types match expectations
- Ensure consistent Java version
- Check for platform-specific code
- Verify all dependencies are available
- Review test database extraction settings
- Check framework version compatibility
- Java Query Development Prompt - Comprehensive Java query development guide
- CodeQL TDD Generic Skill - General test-driven development workflow
- Java QSpec Reference - Java-specific QSpec patterns
- Java AST Documentation - Official Java AST reference
- Generate QSpec for Java - Java QSpec generation guidance
Your Java query unit tests are successful when:
- ✅ Test structure follows conventions
- ✅ Java test code compiles and is valid
- ✅ Test database extracts without errors
- ✅ All tests pass consistently
- ✅ Comprehensive coverage of Java features
- ✅ Framework-specific patterns tested (if applicable)
- ✅ Both positive and negative cases included
- ✅ Edge cases properly handled
- ✅ Expected results accurately reflect query behavior