A lightweight Java library to parse and evaluate rules against objects (Spring Boot compatible).
Short summary: This repo provides utilities to build simple object-rule parsers that evaluate boolean conditions against Java objects. The project uses Gradle and is compatible with Spring Boot apps.
- Features
- Prerequisites
- Build & test
- Testing strategy & suggested tests to add
- Example unit test
- Contributing
- License
- Rule parsing and evaluation against plain Java objects.
- Designed to integrate easily with Spring Boot projects.
- Focus on readability and small API surface so unit testing is straightforward.
- Java 21+ (project currently built with Java 21 compatibility; upgrade if needed).
- Gradle (wrapper included; use
./gradlew). - Git
Build the project and run tests with the Gradle wrapper:
# build and run tests
./gradlew clean build
# run only tests
./gradlew testAim for a mix of the following test types:
-
Pure unit tests (fast, no Spring context)
- Parser logic: verify that given a rule string and an input object the parser returns expected boolean results.
- Edge conditions: null inputs, empty rules, unsupported operators, malformed rule expressions — assert the correct exception or
falseresult depending on intended behaviour. - Rule composition:
AND,OR,NOTcombinations and precedence. - Data type handling: numeric comparisons, strings, enums, booleans, collections.
- Boundary values (e.g., large numbers, empty strings).
-
Integration / slice tests (use minimal Spring features)
- If there are Spring
@Componentor@Serviceclasses that wire the parser, use@ExtendWith(SpringExtension.class)and@ContextConfigurationor@SpringBootTestbut keep them minimal and focused.
- If there are Spring
-
Negative tests / contract tests
- Verify that invalid rules yield controlled exceptions with useful messages.
-
Concurrency / thread-safety tests (if parser is shared across threads)
- Run the same parser concurrently from multiple threads and assert stability / no mutated shared state.
-
Performance smoke tests (optional)
- Run the parser on larger inputs to track performance regressions (these can be gated out of unit test suite).
- Parser basic evaluation (true/false cases).
- Operator coverage (
==,!=,<,>,<=,>=,contains, string ops). - Enum handling (if library reads enums via SpEL or custom mapping).
- Null handling (null object, null fields, missing properties).
- Error messages for malformed rules.
Below is a simple unit testing example demonstrating how to unit test a parser class (RuleParser) — adapt class & method names to match your codebase.
@Test
void parseBussinessRule() throws ScriptException {
RuleParser ruleParser = new RuleParser();
ClientAddress clientAddress = new ClientAddress();
List<Address> addresses = new ArrayList<>();
for (int i=0; i<5; i++) {
City city = new City();
Province province = new Province();
Address address = new Address();
city.setCityName("city number "+i);
city.setCityCode(""+1);
province.setProvinceName("province number "+i);
province.setProvinceCode(""+i);
address.setCity(city);
address.setProvince(province);
address.setAddressName("address name number "+i);
addresses.add(address);
}
clientAddress.setAddresses(addresses);
clientAddress.setDetailAdress("testing melanesian reflection");
Assertions.assertTrue(ruleParser.parseBussinessRule(clientAddress, "<addresses.0.province.provinceName> == 'province number 0'"));
}
@Test
void containsObjectTest() throws ScriptException{
ClientAddress clientAddress = createClientAdress();
RuleParser ruleParser = new RuleParser();
Assertions.assertTrue(ruleParser.parseBussinessRule(clientAddress,
"<containAddressName('address name number 1')> && <addresses.0.province.getProvinceName()> == 'province number 0'"));
}
@Test
void testGetWithSimpleExpressionToUpperCase() {
ClientAddress clientAddress = createClientAdress();
NestedReflection nsReflection = new NestedReflection();
nsReflection.setFactoryOfExpressions(new SimpleExpressionFilter());
Assertions.assertEquals("PROVINCE NUMBER 2", nsReflection.getObject(clientAddress,
"addresses.2.province.provinceName.SimpleExpressionFilter{uppercase}"));
}
@Test
void testGetWithSingleParamArrayFiltering() {
ClientAddress clientAddress = createClientAdress();
Assertions.assertEquals("address name number 0",
nestedReflection.getObject(clientAddress, "addresses.SPArrayFilter{addressName.equals('address name number 0')}.addressName"));
Assertions.assertEquals("address name number 1",
nestedReflection.getObject(clientAddress, "addresses.SPArrayFilter{addressName == 'address name number 1'}.addressName"));
Assertions.assertEquals("address name number 2",
nestedReflection.getObject(clientAddress, "addresses.SPArrayFilter{addressName == 'address name number 2'}.addressName"));
Assertions.assertEquals("address name number 3",
nestedReflection.getObject(clientAddress, "addresses.SPArrayFilter{addressName == 'address name number 3'}.addressName"));
Assertions.assertEquals("address name number 4",
nestedReflection.getObject(clientAddress, "addresses.SPArrayFilter{addressName == 'address name number 4'}.addressName"));
}- Fork the repo.
- Create a feature branch for tests:
git checkout -b feat/add-unit-tests. - Add tests under
src/test/javafollowing package layout. - Run
./gradlew testlocally and ensure all new tests pass. - Create a PR and describe the tests and reasoning.
This project is licensed under the MIT License.