Assertj-core: Allow working with the type previously checked through isInstanceOf(…)

Created on 9 Jun 2016  ·  18Comments  ·  Source: assertj/assertj-core

Summary

When writing assertions against instances of Object, I frequently find myself asserting the actual object type first, and then casting to that type to add further assertions:

Object object = …;

assertThat(object).isInstanceOf(Foo.class);

Foo foo = (Foo) object;

assertThat(foo).…

It would be cool if the instanceOf check returned an assertion for the type I just asserted to:

Example

assertThat(object).isInstanceOf(String.class).contains("bar");

Most helpful comment

This would return an instance of X itself, which would require another assertThat around the call. But if isInstanceOf returned an ObjectAssert<X>, it should work. Something like:

public <X> AbstractObjectAssert<?, X> isInstanceOf(Class<X> type) {
    objects.assertIsInstanceOf(info, actual, type);
    return assertThat(type.cast(actual));
}

Of cause this would mean you couldn't chain any type-specific assertions after isInstanceOf but that's probably ok because you wouldn't use isInstanceOf if you already have a type.

All 18 comments

That would be a nice thing to have indeed, I'm not sure it is technically feasible to with generic type erasure but I'll give it a try anyway.

In the meantime, in case your object is a String, there is an asString() that combines checking the object is an instance of a String a return StringAssert (same with List).

The String was just an example :). In my case I am actually using a complex type. Maybe introducing a new method for exactly that purpose is a better way (also not to break existing code)?

I know it was an example :)

Thinking out loud, we could introduce a method like <ASSERT> toAssert(ASSERT assertionClass) to support that but it is a bit ugly.

No need to hurry! Maybe just leave the ticket flying around for a bit and see if others chime in and have ideas.

No hurry indeed, I have put it for 2.6.0 which is not the next release (2.5.0) to have time to think about it.

I think this is really hitting the limits of Java ;)

Two ideas come to mind:
First idea:

assertThat(typeChecked(object, String.class)).contains("bar");
public static <T> T typeChecked(Object object, Class<T> expectedClass) {
    assertThat(object).isInstanceOf(expectedClass);
    return (T) object;
}

This would make use of the existing overloads of assertThat. Might be hard to integrate this with soft assertions, though.

Second idea:

assertThat(object).isInstanceOf(String.class).satisfies(string-> assertThat(string).contains("bar"));

Where isInstanceOfcould return an AbstractObjectAssert of the expected type. Even simpler, but probably less readable, would be:

assertThat(object).isInstanceOfAndSatifies(String.class, string-> assertThat(string).contains("bar"));

I like the

assertThat(object).isInstanceOf(String.class).satisfies(string -> …);

idea.

in 3.5.0 release (which is 2.5.0 + Java 8 specific features) coming this month, we have added satisfies feature, see https://github.com/joel-costigliola/assertj-core/issues/643#issuecomment-210687011, it will be pushed in master soonish.

If we change isInstanceOf to the below:

public <X> X isInstanceOf(Class<X> type) {
    objects.assertIsInstanceOf(info, actual, type);
    return type.cast(actual);
}

would that be sufficient to get it working with satisfies?

This would return an instance of X itself, which would require another assertThat around the call. But if isInstanceOf returned an ObjectAssert<X>, it should work. Something like:

public <X> AbstractObjectAssert<?, X> isInstanceOf(Class<X> type) {
    objects.assertIsInstanceOf(info, actual, type);
    return assertThat(type.cast(actual));
}

Of cause this would mean you couldn't chain any type-specific assertions after isInstanceOf but that's probably ok because you wouldn't use isInstanceOf if you already have a type.

@lbilger — That's basically was I was looking for 🙃.

As @lbilger said, his suggestion is not addressing the initial issue :

// does not compile because isInstanceOf returns AbstractObjectAssert not StringAssert.
assertThat(object).isInstanceOf(String.class).contains("bar");

I'm gonna continue trying things although my gut feeling is that you need to pass the assert type as a parameter of isInstanceOf in order to chain type specific assertions.

Got something working by introducing ...

// (naming of the method might change)
public <ASSERT> ASSERT withAssertions(Class<ASSERT> type) {
  try {
    return (ASSERT) type.getConstructor(actual.getClass()).newInstance(actual);
  } catch (Exception e) {
    throw new RuntimeException("Fail to switch to " + type.getSimpleName() + " assertions", e);
  }
}

... one can write:

Object object = "foo";
assertThat(object).withAssertions(StringAssert.class).contains("oo")

Another solution would be to add methods like asString for every type AssertJ has assertions for, each method would check the instance type and convert to the proper assert type. This will add a bunch of methods to AbstractAssert but they will be easy to discover since they all follow the pattern asTYPE, concretely if the object under test is a Date you would just to write

Object object = new Date();
assertThat(object).asDate().hasYear(2016);

Comments welcome ;-)

Fixed by adding all possible asXXX methods.

To be completely honest, I think the API is not really nice as it requires you to have all potential possibilities implemented explicitly. As indicated above, I think the requirement never was to get hold of a typed Assert in the first place (or by that wanting to pass the assert type), but only API to get access to the object under test _as the type I just validated against_, so that I can create additional assertions myself:

                                              | or any other method name
assertThat(object).isInstanceOf(String.class).satisfying(string -> {
  // string is of the type I passed in to isInstanceOf(…)
  assertThat(string).… // <- I happily create the typed assertion myself here
})

I see all the support implemented for the types you know of, but what if I come around with an Optional or something that implements Iterable. There's no as…-method I could call then, right? No offense, but it feels a bit like we were throwing out the baby with the bathwater here (creating a lot of new ,explicit API, where something more generic and tiny would've done the trick)?

I missed the main point, fair enough ...
I'll keep the asXXX methods as they are handy for AssertJ known types and will add the type aware satisfying if this is technically possible (not so sure yet):

 assertThat(object).isInstanceOf(String.class).satisfying

otherwise I'll go for:

assertThat(object).isInstanceOfSatifying(String.class, s-> assertThat(s).contains("bar"));

which I'm pretty sure is possible.

I have reverted the asXXX method to avoid bloating the API and added in isInstanceOfSatifying https://github.com/joel-costigliola/assertj-core/commit/baa10e7e7a67d09fabc2f0f20a919d8e0c30b40f.

Hope this is the right one this time !

There's no right or wrong here :). But a big thanks for taking the feedback into account! 👍

Was this page helpful?
0 / 5 - 0 ratings