Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unsafeInstanceOf Quality, closes #155 #156

Merged
merged 1 commit into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
public final class InstanceOf<T> extends QualityComposition<T>
{
/**
* A {@link Quality} that matches when the object under test is an instance of the given class and satisfies the
* given {@link Quality}.
* A {@link Quality} that is satisfied when the object under test is an instance of the given class and satisfies
* the given {@link Quality}.
* <p>
* This provides a type-safe way to downcast and apply a {@link Quality} of a subtype.
*
Expand All @@ -43,6 +43,8 @@ public final class InstanceOf<T> extends QualityComposition<T>
* assertThat(someObject,
* is(instanceOf(Number.class, that(has(Number::intValue, equalTo(1))))));
* }</pre>
*
* @see UnsafeInstanceOf#UnsafeInstanceOf(Class, Quality) for testing generic classes.
*/
public <V extends T> InstanceOf(Class<? extends V> expectation, Quality<? super V> delegate)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2022 dmfs GmbH
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.saynotobugs.confidence.quality.object;

import org.dmfs.srcless.annotations.staticfactory.StaticFactories;
import org.saynotobugs.confidence.Quality;
import org.saynotobugs.confidence.quality.composite.AllOfFailingFast;
import org.saynotobugs.confidence.quality.composite.QualityComposition;

import static org.saynotobugs.confidence.description.LiteralDescription.NEW_LINE;


@StaticFactories(value = "Core", packageName = "org.saynotobugs.confidence.quality")
public final class UnsafeInstanceOf<T> extends QualityComposition<T>
{
/**
* A {@link Quality} that matches when the object under test is an instance of any subclass of the given class and
* satisfies the given {@link Quality}.
* <p>
* This works like {@link InstanceOf} but provides fewer type-safety guarantees allowing you to pass
* {@link Quality} of subtypes of {@code V}. This may be required when testing generic classes, because you'll
* essentially be forced to work with raw types in such case.
*
* <h4>Example</h4>
* <pre>{@code
* Map actual = ...;
*
* assertThat(actual, unsafeInstanceOf(Map.class, Core.<Map<String, Object>>allOf(
* containsEntry("k1", unsafeInstanceOf(Map.class, allOf(
* containsEntry("k11", "v11"),
* containsEntry("k12", "v12")))),
* containsEntry("k2", unsafeInstanceOf(Iterable.class, iterates("v21", "v22"))),
* containsEntry("k3", unsafeInstanceOf(String.class, equalTo("v3"))))));
* }</pre>
* <p>
* <p>
* Be aware that this also allows you to write nonsensical tests like this:
* <pre>{@code
* static class C1 {}
*
* static class C2 extends C1 {
* int bar() {return 2;}
* }
*
* @Test
* void instanceOfTest() {
* Object o = new C1();
* assertThat(o, unsafeInstanceOf(C1.class, has(C2::bar, equalTo(2))));
* }
* }</pre>
* <p>
* In such case, when the Quality is not compatible with the actual type, the test will fail, reporting a
* {@link ClassCastException}.
*/
public <V extends T, Q extends V> UnsafeInstanceOf(Class<V> expectation, Quality<? super Q> delegate)
{
super((Quality<T>) new AllOfFailingFast<>(NEW_LINE, new InstanceOf<>(expectation), delegate));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.saynotobugs.confidence.description.NumberDescription;
import org.saynotobugs.confidence.description.Spaced;
import org.saynotobugs.confidence.description.Text;
import org.saynotobugs.confidence.quality.Core;
import org.saynotobugs.confidence.quality.comparable.GreaterThan;
import org.saynotobugs.confidence.quality.comparable.LessThan;
import org.saynotobugs.confidence.quality.compat.Hamcrest;
Expand All @@ -27,6 +28,9 @@
import org.saynotobugs.confidence.test.quality.Fails;
import org.saynotobugs.confidence.test.quality.Passes;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
Expand Down Expand Up @@ -300,4 +304,36 @@ void testDelegatingFunction()
)
);
}
}


@Test
void testHamcrestIssue107()
{
Map<String, Number> m = new HashMap<String, Number>();
Integer foo = new Integer(6);
m.put("foo", foo);
assertThat(m, containsEntry("foo", foo));
}


@Test
void testHamcrestIssue388()
{
Map actual = Map.of(
"k1", Map.of(
"k11", "v11",
"k12", "v12"),
"k2", List.of("v21", "v22"),
"k3", "v3"
);

assertThat(actual, unsafeInstanceOf(Map.class, Core.<Map<String, Object>>allOf(
containsEntry("k1", unsafeInstanceOf(Map.class, allOf(
containsEntry("k11", "v11"),
containsEntry("k12", "v12")))),
containsEntry("k2", unsafeInstanceOf(Iterable.class, iterates("v21", "v22"))),
containsEntry("k3", unsafeInstanceOf(String.class, equalTo("v3")))
)
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2023 dmfs GmbH
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.saynotobugs.confidence.quality.object;

import org.dmfs.jems2.iterable.Seq;
import org.junit.jupiter.api.Test;
import org.saynotobugs.confidence.quality.composite.AllOf;
import org.saynotobugs.confidence.quality.composite.Has;
import org.saynotobugs.confidence.quality.grammar.That;
import org.saynotobugs.confidence.quality.iterable.Iterates;
import org.saynotobugs.confidence.test.quality.Fails;
import org.saynotobugs.confidence.test.quality.HasDescription;
import org.saynotobugs.confidence.test.quality.Passes;

import static org.saynotobugs.confidence.Assertion.assertThat;

class UnsafeInstanceOfTest
{

@Test
void testDelegate()
{
assertThat(new UnsafeInstanceOf<>(Number.class, new That<>(new Has<>("intValue", Number::intValue, new EqualTo<>(1)))),
new AllOf<>(
new Passes<>(1, 1.001, 1L, 1f),
new Fails<>(0.999, "(1) that had intValue <0>"),
new Fails<>(2, "(1) that had intValue <2>"),
new Fails<>("string", "(0) instance of <class java.lang.String>"),
new Fails<>(new Object(), "(0) instance of <class java.lang.Object>"),
new HasDescription("(0) instance of <class java.lang.Number>\n (1) that has intValue <1>")
));
}

@Test
void testSubClassDelegate()
{
assertThat(new UnsafeInstanceOf<>(Iterable.class, new That<>(new Iterates<>(1, "abc", true))),
new AllOf<>(
new Passes<>(new Seq<Object>(1, "abc", true), new Seq(1, "abc", true)),
new Fails<>(new Seq<Object>(1.1, "abc", true), "(1) that iterated [ 0: <1.1>\n ... ]"),
new Fails<>(2, "(0) instance of <class java.lang.Integer>"),
new Fails<>("string", "(0) instance of <class java.lang.String>"),
new Fails<>(new Object(), "(0) instance of <class java.lang.Object>"),
new HasDescription("(0) instance of <interface java.lang.Iterable>\n (1) that iterates [ 0: <1>,\n 1: \"abc\",\n 2: <true> ]")
));
}
}