# FluentAssert
**Repository Path**: AhooWang/FluentAssert
## Basic Information
- **Project Name**: FluentAssert
- **Description**: FluentAssert是一个为JDK类型提供流畅断言的Kotlin库,使您的测试更易读和富有表现力。该库使用Kotlin扩展函数包装AssertJ断言以获得更好的语法。
- **Primary Language**: Kotlin
- **License**: Apache-2.0
- **Default Branch**: main
- **Homepage**: https://github.com/Ahoo-Wang/FluentAssert
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2025-04-17
- **Last Updated**: 2026-06-30
## Categories & Tags
**Categories**: utils
**Tags**: assert, assertj, fluent-assertions, Kotlin, assertions
## README
# FluentAssert
[](https://github.com/Ahoo-Wang/FluentAssert/blob/main/LICENSE)
[](https://github.com/Ahoo-Wang/FluentAssert/releases)
[](https://central.sonatype.com/artifact/me.ahoo.test/fluent-assert-core)
[](https://app.codacy.com/gh/Ahoo-Wang/FluentAssert/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
[](https://codecov.io/gh/Ahoo-Wang/FluentAssert)
[](https://github.com/Ahoo-Wang/FluentAssert)
[](https://deepwiki.com/Ahoo-Wang/FluentAssert)
FluentAssert is a Kotlin library that provides fluent assertions for JDK types, making your tests more readable and expressive. The library wraps AssertJ assertions with Kotlin extension functions for better syntax.
## Features
- **Fluent API**: Chain assertions in a readable, natural way
- **Null-Safe**: All extension functions handle nullable types appropriately
- **Comprehensive Coverage**: Supports all major JDK types and collections
- **Type-Safe**: Full Kotlin type system integration
- **AssertJ Powered**: Leverages the robust AssertJ assertion library
- **Zero Dependencies**: Only depends on AssertJ (transitively)
- **Kotlin Idiomatic**: Designed specifically for Kotlin developers
- **IDE Support**: Full IntelliJ IDEA and Android Studio integration
## Installation
### Requirements
- **Java**: 17 or higher
- **Kotlin**: 1.8.0 or higher
- **JUnit**: 5.x (for testing)
### Maven
```xml
me.ahoo.test
fluent-assert-core
1.0.0
test
```
### Gradle (Kotlin DSL)
```kotlin
testImplementation("me.ahoo.test:fluent-assert-core:1.0.0")
```
### Gradle (Groovy DSL)
```gradle
testImplementation 'me.ahoo.test:fluent-assert-core:1.0.0'
```
## Quick Start
Simply call `.assert()` on any JDK type to start building fluent assertions:
```kotlin
import me.ahoo.test.asserts.assert
// Basic assertions
val name = "FluentAssert"
name.assert().startsWith("Fluent").endsWith("Assert")
val age = 25
age.assert().isGreaterThan(18).isLessThan(100)
val isActive = true
isActive.assert().isTrue()
```
## API Reference
### Core Extension Functions
All extension functions follow the pattern `Type.assert(): AssertJTypeAssert`, where:
- `Type` is any supported JDK type (nullable or non-nullable)
- `AssertJTypeAssert` is the corresponding AssertJ assertion class
### Supported Types
| Category | Types |
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Primitives** | `Boolean`, `Byte`, `Short`, `Int`, `Long`, `Float`, `Double`, `BigDecimal` |
| **Text** | `String` |
| **Collections** | `Iterable`, `Iterator`, `Collection`, `Array`, `List`, `Map`, `Optional`, `Stream` |
| **Time/Date** | `Date`, `ZonedDateTime`, `LocalDateTime`, `OffsetDateTime`, `OffsetTime`, `LocalTime`, `LocalDate`, `YearMonth`, `Instant`, `Duration`, `Period`, `Temporal` |
| **I/O** | `Path`, `File`, `URL`, `URI` |
| **Concurrent** | `Future`, `CompletableFuture`, `CompletionStage` |
| **Functional** | `Predicate` |
| **Exceptions** | `Throwable` |
| **Custom** | `AssertProvider` |
### Exception Testing Functions
- `assertThrownBy(shouldRaiseThrowable: () -> Unit): ThrowableAssert` - Asserts that code throws a specific exception type
- `Throwable.assert(): ThrowableAssert` - Creates assertions for exception instances
## Complete API Reference
### Core JDK Types
#### Primitive Types
##### `Boolean?.assert(): BooleanAssert`
Creates assertions for boolean values.
```kotlin
true.assert().isTrue()
false.assert().isFalse()
val nullableBool: Boolean? = null
nullableBool.assert().isNull()
```
##### `Byte?.assert(): ByteAssert`
Creates assertions for byte values.
```kotlin
val value: Byte = 42
value.assert().isEqualTo(42).isPositive()
```
##### `Short?.assert(): ShortAssert`
Creates assertions for short values.
```kotlin
val value: Short = 1000
value.assert().isEqualTo(1000).isGreaterThan(0)
```
##### `Int?.assert(): IntegerAssert`
Creates assertions for integer values.
```kotlin
val age = 25
age.assert().isEqualTo(25).isBetween(18, 65)
```
##### `Long?.assert(): LongAssert`
Creates assertions for long values.
```kotlin
val timestamp = System.currentTimeMillis()
timestamp.assert().isPositive().isGreaterThan(0)
```
##### `Float?.assert(): FloatAssert`
Creates assertions for float values.
```kotlin
val pi = 3.14f
pi.assert().isEqualTo(3.14f).isPositive()
```
##### `Double?.assert(): DoubleAssert`
Creates assertions for double values.
```kotlin
val price = 19.99
price.assert().isEqualTo(19.99).isPositive()
```
##### `BigDecimal?.assert(): BigDecimalAssert`
Creates assertions for BigDecimal values.
```kotlin
val amount = BigDecimal("123.45")
amount.assert().isEqualTo("123.45").isPositive()
```
#### Text Types
##### `String?.assert(): StringAssert`
Creates assertions for string values.
```kotlin
val name = "FluentAssert"
name.assert()
.startsWith("Fluent")
.endsWith("Assert")
.contains("uentAss")
.hasLength(11)
```
#### Generic Types
##### ` T?.assert(): ObjectAssert`
Creates assertions for any object type.
```kotlin
val person = Person("John", 30)
person.assert()
.isNotNull()
.hasFieldOrPropertyWithValue("name", "John")
```
##### `?> T.assert(): GenericComparableAssert`
Creates assertions for comparable objects.
```kotlin
val version = "2.0.0"
version.assert()
.isGreaterThan("1.0.0")
.isLessThan("3.0.0")
```
### Collections
##### ` Iterable?.assert(): IterableAssert`
Creates assertions for iterable collections.
```kotlin
val numbers = listOf(1, 2, 3, 4, 5)
numbers.assert()
.hasSize(5)
.contains(3)
.doesNotContain(6)
.allMatch { it > 0 }
```
##### ` Iterator?.assert(): IteratorAssert`
Creates assertions for iterators.
```kotlin
val iterator = listOf(1, 2, 3).iterator()
iterator.assert().hasNext()
```
##### ` Collection?.assert(): CollectionAssert`
Creates assertions for collections.
```kotlin
val set = setOf("apple", "banana", "orange")
set.assert()
.hasSize(3)
.contains("apple")
.doesNotContain("grape")
```
##### ` Array?.assert(): ObjectArrayAssert`
Creates assertions for arrays.
```kotlin
val array = arrayOf("a", "b", "c")
array.assert()
.hasSize(3)
.contains("b")
.doesNotContain("d")
```
##### ` List?.assert(): ListAssert`
Creates assertions for lists.
```kotlin
val items = listOf("apple", "banana", "orange")
items.assert()
.hasSize(3)
.contains("apple", "banana")
.element(0).isEqualTo("apple")
```
##### ` Optional?.assert(): OptionalAssert`
Creates assertions for Optional values.
```kotlin
val present = Optional.of("value")
present.assert()
.isPresent()
.contains("value")
val empty = Optional.empty()
empty.assert().isEmpty()
```
##### ` Map?.assert(): MapAssert`
Creates assertions for maps.
```kotlin
val map = mapOf("key1" to "value1", "key2" to "value2")
map.assert()
.hasSize(2)
.containsKey("key1")
.containsValue("value1")
.containsEntry("key1", "value1")
```
##### ` Stream?.assert(): ListAssert`
Creates assertions for streams (converted to lists).
```kotlin
val stream = listOf(1, 2, 3, 4, 5).stream()
stream.assert()
.hasSize(5)
.contains(3)
.allMatch { it > 0 }
```
### Time and Date
##### `Date?.assert(): DateAssert`
Creates assertions for Date objects.
```kotlin
val date = Date()
date.assert()
.isToday()
.isBefore(Date(System.currentTimeMillis() + 1000))
```
##### `ZonedDateTime?.assert(): ZonedDateTimeAssert`
Creates assertions for ZonedDateTime objects.
```kotlin
val zonedDateTime = ZonedDateTime.now()
zonedDateTime.assert()
.isToday()
.hasZone(ZoneId.systemDefault())
```
##### `Temporal?.assert(): TemporalAssert`
Creates assertions for any Temporal objects.
```kotlin
val instant = Instant.now()
instant.assert()
.isBefore(Instant.now().plusSeconds(1))
```
##### `LocalDateTime?.assert(): LocalDateTimeAssert`
Creates assertions for LocalDateTime objects.
```kotlin
val dateTime = LocalDateTime.now()
dateTime.assert()
.isToday()
.isBefore(LocalDateTime.now().plusHours(1))
```
##### `OffsetDateTime?.assert(): OffsetDateTimeAssert`
Creates assertions for OffsetDateTime objects.
```kotlin
val offsetDateTime = OffsetDateTime.of(2023, 12, 25, 10, 30, 0, 0, java.time.ZoneOffset.UTC)
offsetDateTime.assert()
.isEqualTo(OffsetDateTime.of(2023, 12, 25, 10, 30, 0, 0, java.time.ZoneOffset.UTC))
.isBefore(offsetDateTime.plusDays(1))
```
##### `OffsetTime?.assert(): OffsetTimeAssert`
Creates assertions for OffsetTime objects.
```kotlin
val offsetTime = OffsetTime.now()
offsetTime.assert()
.isBefore(OffsetTime.now().plusHours(1))
```
##### `LocalTime?.assert(): LocalTimeAssert`
Creates assertions for LocalTime objects.
```kotlin
val time = LocalTime.of(10, 30)
time.assert()
.isBefore(LocalTime.of(12, 0))
.hasHour(10)
.hasMinute(30)
```
##### `LocalDate?.assert(): LocalDateAssert`
Creates assertions for LocalDate objects.
```kotlin
val date = LocalDate.of(2023, 12, 25)
date.assert()
.hasYear(2023)
.hasMonth(java.time.Month.DECEMBER)
.hasDayOfMonth(25)
```
##### `YearMonth?.assert(): YearMonthAssert`
Creates assertions for YearMonth objects.
```kotlin
val yearMonth = YearMonth.of(2023, 12)
yearMonth.assert()
.hasYear(2023)
.hasMonth(java.time.Month.DECEMBER)
```
##### `Instant?.assert(): InstantAssert`
Creates assertions for Instant objects.
```kotlin
val instant = Instant.now()
instant.assert()
.isBefore(Instant.now().plusSeconds(1))
```
##### `Duration?.assert(): DurationAssert`
Creates assertions for Duration objects.
```kotlin
val duration = Duration.ofHours(2)
duration.assert()
.hasHours(2)
.isGreaterThan(Duration.ofHours(1))
```
##### `Period?.assert(): PeriodAssert`
Creates assertions for Period objects.
```kotlin
val period = Period.of(1, 2, 3)
period.assert()
.hasYears(1)
.hasMonths(2)
.hasDays(3)
```
### File System and I/O
##### `Path?.assert(): PathAssert`
Creates assertions for Path objects.
```kotlin
val path = Paths.get("/tmp/test.txt")
path.assert()
.exists()
.isReadable()
.isRegularFile()
```
##### `File?.assert(): FileAssert`
Creates assertions for File objects.
```kotlin
val file = File("/tmp/test.txt")
file.assert()
.exists()
.isFile()
.canRead()
.hasName("test.txt")
```
##### `URL?.assert(): UrlAssert`
Creates assertions for URL objects.
```kotlin
val url = URL("https://example.com")
url.assert()
.hasHost("example.com")
.hasProtocol("https")
.hasPort(443)
```
##### `URI?.assert(): UriAssert`
Creates assertions for URI objects.
```kotlin
val uri = URI("https://example.com/path?query=value")
uri.assert()
.hasHost("example.com")
.hasPath("/path")
.hasQuery("query=value")
```
### Concurrent Programming
##### ` Future?.assert(): FutureAssert`
Creates assertions for Future objects.
```kotlin
val future = executor.submit(Callable { "result" })
future.assert()
.isDone()
.isNotCancelled()
```
##### ` CompletableFuture?.assert(): CompletableFutureAssert`
Creates assertions for CompletableFuture objects.
```kotlin
val future = CompletableFuture.completedFuture("success")
future.assert()
.isCompleted()
.isCompletedWithValue("success")
```
##### ` CompletionStage?.assert(): CompletionStageAssert`
Creates assertions for CompletionStage objects.
```kotlin
val stage = CompletableFuture.completedFuture("result")
stage.assert()
.isCompleted()
.isCompletedWithValue("result")
```
### Functional Programming
##### ` Predicate?.assert(): PredicateAssert`
Creates assertions for Predicate functions.
```kotlin
val isEven = Predicate { it % 2 == 0 }
isEven.assert()
.accepts(2, 4, 6)
.rejects(1, 3, 5)
```
### Custom Assertion Providers
##### ` AssertProvider.assert(): A`
Creates assertions for objects implementing AssertJ's `AssertProvider` interface. Delegates to `assertThat(this)` to obtain the provider's custom assertion type.
```kotlin
class MoneyAssertProvider(private val amount: BigDecimal) : AssertProvider {
override fun assertThat(): BigDecimalAssert = assertThat(amount)
}
val provider = MoneyAssertProvider(BigDecimal("99.99"))
provider.assert().isPositive().isLessThan(BigDecimal("100"))
```
### Exception Testing
##### ` T?.assert(): ThrowableAssert`
Creates assertions for Throwable objects.
```kotlin
val exception = RuntimeException("test error")
exception.assert()
.hasMessage("test error")
.isInstanceOf(RuntimeException::class.java)
```
##### `assertThrownBy(Class, () -> Unit): ThrowableAssert`
Asserts that code throws a specific exception type.
```kotlin
assertThrownBy(IllegalArgumentException::class.java) {
throw IllegalArgumentException("invalid argument")
}.hasMessage("invalid argument")
```
##### `assertThrownBy(() -> Unit): ThrowableAssert` (reified)
Asserts that code throws a specific exception type (Kotlin reified version).
```kotlin
assertThrownBy {
throw IllegalArgumentException("invalid argument")
}.hasMessage("invalid argument")
```
## Comparison with AssertJ
FluentAssert is built on top of AssertJ and provides additional benefits:
| Feature | AssertJ | FluentAssert |
|-------------------------|-----------------------------------------|--------------------------------------|
| **Syntax** | `assertThat(value).isEqualTo(expected)` | `value.assert().isEqualTo(expected)` |
| **Null Safety** | Manual null checks | Automatic null handling |
| **Kotlin Integration** | Java library | Kotlin-first design |
| **Extension Functions** | Not applicable | Full Kotlin extension support |
| **Type Inference** | Limited | Enhanced Kotlin type system |
| **IDE Support** | Good | Excellent (Kotlin-aware) |
### When to Use FluentAssert
- ✅ **Kotlin projects** - Better integration with Kotlin idioms
- ✅ **Null-heavy code** - Automatic null safety
- ✅ **Fluent style preference** - More readable assertion chains
- ✅ **Type-safe assertions** - Leverage Kotlin's type system
### When to Use AssertJ Directly
- 🔸 **Java projects** - No need for Kotlin extensions
- 🔸 **Custom assertion logic** - Direct AssertJ gives more control
- 🔸 **Performance-critical code** - Minimal overhead
## Advanced Examples
### Complex Business Logic Validation
```kotlin
// User registration validation
data class User(val name: String, val age: Int, val email: String)
fun validateUser(user: User) {
user.name.assert()
.isNotBlank()
.hasSizeBetween(2, 50)
.matches("[a-zA-Z\\s]+")
user.age.assert()
.isBetween(13, 120)
user.email.assert()
.isNotBlank()
.matches("[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}")
}
// Usage
val user = User("John Doe", 25, "john@example.com")
validateUser(user)
```
### Data Processing Pipeline Testing
```kotlin
fun processData(input: List): List {
return input
.filter { it.isNotBlank() }
.map { it.uppercase() }
.distinct()
.sorted()
}
fun testDataProcessing() {
val input = listOf(" apple", "", "banana", " APPLE", "cherry")
val result = processData(input)
result.assert()
.hasSize(3)
.contains("APPLE", "BANANA", "CHERRY")
.isSorted()
.allMatch { it == it.uppercase() }
}
```
### Concurrent Operation Testing
```kotlin
suspend fun testAsyncOperations() {
val results = coroutineScope {
val deferred1 = async { fetchUser(1) }
val deferred2 = async { fetchUser(2) }
listOf(deferred1, deferred2).awaitAll()
}
results.assert()
.hasSize(2)
.allMatch { it != null }
.anySatisfy { user ->
user.name.assert().isEqualTo("John Doe")
user.id.assert().isPositive()
}
}
```
### Configuration Validation
```kotlin
data class DatabaseConfig(
val host: String,
val port: Int,
val database: String,
val credentials: Map
)
fun validateConfig(config: DatabaseConfig) {
config.host.assert()
.isNotBlank()
.matches("^[a-zA-Z0-9.-]+$")
config.port.assert()
.isBetween(1024, 65535)
config.database.assert()
.isNotBlank()
.hasSizeBetween(1, 64)
config.credentials.assert()
.isNotEmpty()
.containsKey("username")
.containsKey("password")
.allSatisfy { (key, value) ->
key.assert().isNotBlank()
value.assert().isNotBlank()
}
}
```
## Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
### Development Setup
1. **Clone the repository**
```bash
git clone https://github.com/Ahoo-Wang/FluentAssert.git
cd FluentAssert
```
2. **Build the project**
```bash
./gradlew build
```
3. **Run tests**
```bash
./gradlew test
```
4. **Run linting**
```bash
./gradlew detekt
```
### Code Style
- Follow Kotlin official coding conventions
- Use 300 characters maximum line length
- Write comprehensive tests for all public APIs
- Use descriptive test method names with backticks
### Pull Request Process
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## Frequently Asked Questions
### General Questions
**Q: What is FluentAssert?**
A: FluentAssert is a Kotlin library that provides fluent assertions for JDK types, making your unit tests more readable and expressive by wrapping AssertJ assertions with Kotlin extension functions.
**Q: Why should I use FluentAssert instead of AssertJ directly?**
A: FluentAssert provides a more Kotlin-idiomatic API with better null safety, type inference, and IDE support. The `.assert()` syntax is more fluent and readable than AssertJ's `assertThat()`.
**Q: Is FluentAssert production-ready?**
A: Yes, FluentAssert is stable and ready for production use. It has comprehensive test coverage and follows semantic versioning.
### Technical Questions
**Q: Does FluentAssert add runtime overhead?**
A: Minimal. The extension functions are inlined where possible, and the library simply delegates to AssertJ, which is highly optimized.
**Q: Can I use FluentAssert with other testing frameworks?**
A: Yes, FluentAssert works with any testing framework that supports AssertJ assertions, including JUnit 5, TestNG, and Spock.
**Q: How does null handling work?**
A: All extension functions accept nullable types and handle null values appropriately, providing null-safe assertions without additional boilerplate.
**Q: What JDK versions are supported?**
A: FluentAssert supports Java 17 and higher, with full compatibility with all modern JDK types and APIs.
### Usage Questions
**Q: How do I migrate from AssertJ to FluentAssert?**
A: Replace `assertThat(value)` with `value.assert()`. The assertion methods remain the same.
**Q: Can I mix FluentAssert and AssertJ in the same test?**
A: Yes, they are fully compatible. You can use both APIs in the same codebase.
**Q: Are there any limitations compared to AssertJ?**
A: FluentAssert provides access to all AssertJ functionality. Some advanced AssertJ features may require direct AssertJ usage, but this is rare.
### Troubleshooting
**Q: I'm getting compilation errors. What should I check?**
A: Ensure you're using Java 17+, Kotlin 1.8.0+, and have the correct dependencies. Check that your IDE is using the right JDK.
**Q: Tests are failing with null pointer exceptions.**
A: This usually means you're calling methods on null values. Use safe calls (`?.`) or check for null before assertions.
**Q: IDE doesn't recognize the extension functions.**
A: Ensure the FluentAssert dependency is properly configured and your IDE has Kotlin plugin updated.
### Contributing
**Q: How can I contribute new assertion types?**
A: See our [Contributing Guide](CONTRIBUTING.md) for detailed instructions on adding new JDK type support.
**Q: Can I suggest new features?**
A: Absolutely! Open an issue on GitHub with the "enhancement" label to discuss new features.
**Q: Found a bug. How do I report it?**
A: Create an issue on GitHub with detailed steps to reproduce, expected vs actual behavior, and your environment details.
## License
FluentAssert is licensed under the [Apache License 2.0](LICENSE).