Skip to content

Commit

Permalink
support-ranges (#3735)
Browse files Browse the repository at this point in the history
* support-ranges

* Update documentation/docs/assertions/ranges.md

Co-authored-by: Leonardo Colman Lopes <dev@leonardo.colman.com.br>

* Update kotest-assertions/kotest-assertions-core/src/jvmMain/kotlin/io/kotest/matchers/ranges/bein.kt

Co-authored-by: Leonardo Colman Lopes <dev@leonardo.colman.com.br>

* Update kotest-assertions/kotest-assertions-core/src/jvmMain/kotlin/io/kotest/matchers/ranges/bein.kt

Co-authored-by: Leonardo Colman Lopes <dev@leonardo.colman.com.br>

* restore-gradle-properties

* move-to-common-module

* Update gradle.properties

* Be in support closed range take2 (#1)

---------

Co-authored-by: Leonardo Colman Lopes <dev@leonardo.colman.com.br>
  • Loading branch information
AlexCue987 and LeoColman committed Oct 20, 2023
1 parent 629e382 commit eab0f88
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 0 deletions.
14 changes: 14 additions & 0 deletions documentation/docs/assertions/ranges.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
id: ranges
title: Range Matchers
slug: range-matchers.html
sidebar_label: Ranges
---

This page describes the rich assertions (matchers) that are available for [ClosedRange](https://kotlinlang.org/docs/ranges.html) and [OpenEndRange](https://kotlinlang.org/docs/ranges.html) types.


| Ranges | |
|------------------------------|-------------------------------------------------------------------------------------------|
| `value.shouldBeIn(range)` | Asserts that an object is contained in range, checking by value and not by reference. |
| `value.shouldNotBeIn(range)` | Asserts that an object is not contained in range, checking by value and not by reference. |
Original file line number Diff line number Diff line change
Expand Up @@ -1728,6 +1728,12 @@ public final class io/kotest/matchers/property/MatchersKt {
public static final fun shouldBeNullable (Lkotlin/reflect/KProperty;)V
}

public final class io/kotest/matchers/ranges/BeinKt {
public static final fun beIn (Lkotlin/ranges/ClosedRange;)Lio/kotest/matchers/Matcher;
public static final fun shouldBeIn (Ljava/lang/Comparable;Lkotlin/ranges/ClosedRange;)Ljava/lang/Comparable;
public static final fun shouldNotBeIn (Ljava/lang/Comparable;Lkotlin/ranges/ClosedRange;)Ljava/lang/Comparable;
}

public final class io/kotest/matchers/reflection/CallableMatchersKt {
public static final fun acceptParametersOfType (Ljava/util/List;)Lio/kotest/matchers/Matcher;
public static final fun beAbstract ()Lio/kotest/matchers/Matcher;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.kotest.matchers.ranges

import io.kotest.assertions.print.print
import io.kotest.matchers.Matcher
import io.kotest.matchers.MatcherResult
import io.kotest.matchers.should
import io.kotest.matchers.shouldNot

/**
* Verifies that this element is in [ClosedRange] by comparing value
*
* Assertion to check that this element is in [ClosedRange]. This assertion checks by value, and not by reference,
* therefore even if the exact instance is not in [ClosedRange] but another instance with same value is present, the
* test will pass.
*
* An empty range will always fail. If you need to check for empty range, use [ClosedRange.shouldBeEmpty]
*
* @see [shouldNotBeIn]
* @see [beIn]
*/
infix fun <T: Comparable<T>> T.shouldBeIn(range: ClosedRange<T>): T {
this should beIn(range)
return this
}

/**
* Verifies that this element is NOT any of [range]
*
* Assertion to check that this element is not any of [range]. This assertion checks by value, and not by reference,
* therefore any instance with same value must not be in [range], or this will fail.
*
* An empty range will always fail. If you need to check for empty range, use [Iterable.shouldBeEmpty]
*
* @see [shouldNotBeIn]
* @see [beIn]
*/
infix fun <T: Comparable<T>> T.shouldNotBeIn(range: ClosedRange<T>): T {
this shouldNot beIn(range)
return this
}

/**
* Matcher that verifies that this element is in [range] by comparing value
*
* Assertion to check that this element is in [range]. This assertion checks by value, and not by reference,
* therefore even if the exact instance is not in [range] but another instance with same value is present, the
* test will pass.
*
* An empty range will always fail. If you need to check for empty range, use [Iterable.shouldBeEmpty]
*
*/
fun <T: Comparable<T>> beIn(range: ClosedRange<T>) = object : Matcher<T> {
override fun test(value: T): MatcherResult {
if (range.isEmpty()) throw AssertionError("Asserting content on empty range. Use Iterable.shouldBeEmpty() instead.")

val match = value in range

return MatcherResult(
match,
{ "Range should contain ${value.print().value}, but doesn't. Possible values: ${range.print().value}" },
{ "Range should not contain ${value.print().value}, but does. Forbidden values: ${range.print().value}" }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ class BeEmptyTest : WordSpec() {
arrayOf<Int>().shouldBeEmpty()
}

"succeed for empty range" {
(1..0).shouldBeEmpty()
}

"succeed for empty open range" {
(1 until 1).shouldBeEmpty()
}

"fail for single element list" {
shouldThrowAny {
listOf(0).shouldBeEmpty()
Expand All @@ -53,6 +61,18 @@ class BeEmptyTest : WordSpec() {
}.message shouldBe "Sequence should be empty"
}

"fail for single element range" {
shouldThrowAny {
(1..1).shouldBeEmpty()
}.message shouldBe "Collection should be empty but contained 1"
}

"fail for single element open range" {
shouldThrowAny {
(1 until 2).shouldBeEmpty()
}.message shouldBe "Collection should be empty but contained 1"
}

"fail for sequence of nulls" {
shouldThrowAny {
sequenceOf<Int?>(null, null, null, null).shouldBeEmpty()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.sksamuel.kotest.matchers.ranges

import io.kotest.assertions.throwables.shouldThrowAny
import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.ranges.shouldBeIn
import io.kotest.matchers.ranges.shouldNotBeIn
import io.kotest.matchers.shouldBe

class ShouldBeInTest : WordSpec() {
private val closedRange = 1..3
private val openEndRange = 1 until 3

init {
"shouldBeIn" should {
"fail before left end of closed range" {
shouldThrowAny {
0 shouldBeIn closedRange
}.message shouldBe "Range should contain 0, but doesn't. Possible values: [1, 2, 3]"
}

"fail before left end of open end range" {
shouldThrowAny {
0 shouldBeIn openEndRange
}.message shouldBe "Range should contain 0, but doesn't. Possible values: [1, 2]"
}

"succeed on left end of closed range" {
1 shouldBeIn closedRange
}

"succeed on left end of open end range" {
1 shouldBeIn openEndRange
}

"succeed inside closed range" {
2 shouldBeIn closedRange
}

"succeed inside open end range" {
2 shouldBeIn openEndRange
}

"succeed on right end of closed range" {
3 shouldBeIn closedRange
}

"fail on right end of open end range" {
shouldThrowAny {
3 shouldBeIn openEndRange
}.message shouldBe "Range should contain 3, but doesn't. Possible values: [1, 2]"
}

"fail after right end of closed range" {
shouldThrowAny {
4 shouldBeIn closedRange
}.message shouldBe "Range should contain 4, but doesn't. Possible values: [1, 2, 3]"
}

"fail after right end of open end range" {
shouldThrowAny {
4 shouldBeIn openEndRange
}.message shouldBe "Range should contain 4, but doesn't. Possible values: [1, 2]"
}
}

"shouldNotBeIn" should {
"succeed before left end of closed range" {
0 shouldNotBeIn closedRange
}

"succeed before left end of open end range" {
0 shouldNotBeIn openEndRange
}

"fail on left end of closed range" {
shouldThrowAny {
1 shouldNotBeIn closedRange
}.message shouldBe "Range should not contain 1, but does. Forbidden values: [1, 2, 3]"
}

"fail on left end of open end range" {
shouldThrowAny {
1 shouldNotBeIn openEndRange
}.message shouldBe "Range should not contain 1, but does. Forbidden values: [1, 2]"
}

"fail inside closed range" {
shouldThrowAny {
2 shouldNotBeIn closedRange
}.message shouldBe "Range should not contain 2, but does. Forbidden values: [1, 2, 3]"
}

"fail inside open end range" {
shouldThrowAny {
2 shouldNotBeIn openEndRange
}.message shouldBe "Range should not contain 2, but does. Forbidden values: [1, 2]"
}

"fail on right end of closed range" {
shouldThrowAny {
3 shouldNotBeIn closedRange
}.message shouldBe "Range should not contain 3, but does. Forbidden values: [1, 2, 3]"
}

"succeed on right end of open end range" {
3 shouldNotBeIn openEndRange
}

"succeed after right end of closed range" {
3 shouldNotBeIn openEndRange
}

"succeed after right end of open end range" {
3 shouldNotBeIn openEndRange
}
}
}
}

0 comments on commit eab0f88

Please sign in to comment.