-
Notifications
You must be signed in to change notification settings - Fork 69
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
Document Best Practices For Dealing With Inheritance #137
Comments
Interesting ! I have a simple solution to allow chaining but it's far from ideal, you need to override the inherited assertion like this : @Override
public EmployeeAssert hasName(String name) {
return (EmployeeAssert) super.hasName(name);
} Solving the problem with generics would avoid the need to override assertions but I'm not sure it's feasible. |
Ok found how to solve this with generics ! First generify public class HumanAssert<T extends HumanAssert<T>> extends AbstractAssert<HumanAssert<T>, Human> {
public HumanAssert(Human actual) {
super(actual, HumanAssert.class);
}
public T hasName(String name) {
isNotNull();
assertThat(actual.name).isEqualTo(name);
return (T) this;
}
} then change class EmployeeAssert extends HumanAssert { to : public class EmployeeAssert extends HumanAssert<EmployeeAssert> { This solves the chaining problem at the cost of ugly code ... |
I have got another potential solution : using fest-assertion-generator. fest-assertion-generator can generate for you |
I may have to go with the generator. I don't think your solution supports a more complex test case like this: public class Example {
public static void main(String... args) {
Human h = new Human("Jake");
assertThat(h).hasName("Jake");
Employee e = new Employee("Jake", "Engineer");
assertThat(e).hasJobTitle("Engineer").hasName("Jake");
assertThat(e).hasName("Jake").hasTitle("Engineer");
UnitedStatesEmployee u = new UnitedStatesEmployee("Jake", "Engineer", "California");
assertThat(u).isInState("California").hasJobTitle("Engineer").hasName("Jake");
assertThat(u).hasJobTitle("Engineer").isInState("California").hasName("Jake");
assertThat(u).hasJobTitle("Engineer").hasName("Jake").isInState("California");
assertThat(u).hasName("Jake").isInState("California").hasJobTitle("Engineer");
assertThat(u).hasName("Jake").hasJobTitle("Engineer").isInState("California");
}
@SuppressWarnings("unchecked")
static HumanAssert assertThat(Human actual) {
return new HumanAssert(actual, HumanAssert.class);
}
@SuppressWarnings("unchecked")
static EmployeeAssert assertThat(Employee actual) {
return new EmployeeAssert(actual, EmployeeAssert.class);
}
@SuppressWarnings("unchecked")
static UnitedStatesEmployeeAssert assertThat(UnitedStatesEmployee actual) {
return new UnitedStatesEmployeeAssert(actual, UnitedStatesEmployeeAssert.class);
}
}
class Human {
public final String name;
public Human(String name) {
this.name = name;
}
}
class Employee extends Human {
public final String jobTitle;
public Employee(String name, String jobTitle) {
super(name);
this.jobTitle = jobTitle;
}
}
class UnitedStatesEmployee extends Employee {
public final String state;
UnitedStatesEmployee(String name, String title, String state) {
super(name, title);
this.state = state;
}
} |
I solved it with this: class AbstractHumanAssert<S extends AbstractHumanAssert<S, A>, A extends Human> extends AbstractAssert<S, A> {
public AbstractHumanAssert(A actual, Class<S> selfType) {
super(actual, selfType);
}
public S hasName(String name) {
isNotNull();
Assertions.assertThat(actual.name).isEqualTo(name);
return myself;
}
}
class HumanAssert extends AbstractHumanAssert<HumanAssert, Human> {
public HumanAssert(Human actual) {
super(actual, HumanAssert.class);
}
}
class AbstractEmployeeAssert<S extends AbstractEmployeeAssert<S, A>, A extends Employee> extends
AbstractHumanAssert<S, A> {
public AbstractEmployeeAssert(A actual, Class<S> selfType) {
super(actual, selfType);
}
public S hasJobTitle(String title) {
isNotNull();
Assertions.assertThat(actual.jobTitle).isEqualTo(title);
return myself;
}
}
class EmployeeAssert extends AbstractEmployeeAssert<EmployeeAssert, Employee> {
public EmployeeAssert(Employee actual) {
super(actual, EmployeeAssert.class);
}
}
class AbstractUnitedStatesEmployeeAssert<S extends AbstractUnitedStatesEmployeeAssert<S, A>, A extends UnitedStatesEmployee>
extends AbstractEmployeeAssert<S, A> {
public AbstractUnitedStatesEmployeeAssert(A actual, Class<S> selfType) {
super(actual, selfType);
}
public S isInState(String state) {
isNotNull();
Assertions.assertThat(actual.state).isEqualTo(state);
return myself;
}
}
class UnitedStatesEmployeeAssert
extends AbstractUnitedStatesEmployeeAssert<UnitedStatesEmployeeAssert, UnitedStatesEmployee> {
public UnitedStatesEmployeeAssert(UnitedStatesEmployee actual) {
super(actual, UnitedStatesEmployeeAssert.class);
}
} Certainly not ideal, but it works for all permutations listed in the previous comment. |
Using generics, I don't think we can do better than your last solution. Thanks for reporting this ! |
Nice! For example if I extend ListAssert I will lose fluent chaining after first method not from my new class. If my class could extend for example AbstractListAssert with all "lists" methods, it would be much easier. Regards, great lib! |
Perhaps switching them should be considered. I used this technique extensively for square/fest-android which has a heavily nested hierarchy of classes. |
Most (if not all) of the class types for which assertions are provided for have a parent type of
Object
. In practice, the types for which custom assertions will be written will likely have a much more complex hierarchy of classes. How should we deal with this case?An example, for clarity:
Now, how to craft
EmployeeAssert
? Thus far I've been doing the following:This mostly works and allows chained assertions like:
but does not allow:
since the return type of
hasName
isHumanAssert
rather thanEmployeeAssert
.If you extrapolate this case to hierarchies that are three or four classes deep you can quickly see it becomes a mess in forcing your assertions to be written in descending order of the hierarchy if you want to continue chaining.
The text was updated successfully, but these errors were encountered: