assertj / assertj-assertions-generator

Custom assertions generator
http://joel-costigliola.github.io/assertj/assertj-assertions-generator.html
Apache License 2.0
61 stars 42 forks source link

Using annotation processor #96

Open filiphr opened 7 years ago

filiphr commented 7 years ago

Have you considered having an annotation processor that can be used for the generator?

I think that it can be done without any direct dependency on AssertJ in the sources. The supported annotation can be some user defined annotation that would be found via an SPI, the configuration parameters can be controlled via APT options.

I haven't looked at the code, but I assume that the entire logic is in here, the maven-plugin just passes the options to the generator that does everything.

Is this something that would be interesting for the generator?

joel-costigliola commented 7 years ago

So you would annotate domain classes ?

Can you give an example of how you see it working from the user point of view ?

filiphr commented 7 years ago

So you would annotate domain classes ?

Yes. The thing is most of the domain classes are already annotated somehow (JPA @Entity, Lombok @Data, etc). My idea is to allow the user which annotations should the APT use to generate the Assertions. This way they don't have to depend on AssertJ in production code. If it is needed we can provide a module that only has the needed annotations (we do this in mapstruct).

Can you give an example of how you see it working from the user point of view ?

AssertJ code:

interface AssertJGeneratorProvider {

    List<Annotation> getSupportedAnnotations();
}

User code:


@Entity
class MyEntity {
 //fields
}

@UserCustomAnnotation
class MyEntityDto {
 //fields
}

Then there will be an SPI;

class MyAssertJGeneratorProvider implements AssertJGeneratorProvider {

    @Override
    public List<Annotation> getSupportedAnnotations() {
        return Arrays.asList(Entity.class, UserCustomAnnotation.class);
    }
}

The user will also need to provide a file named META-INF/services/org.assertj.generator.ap.spi.AssertJGeneratorProvider with the fully qualified name of the custom implementation (MyAssertJGeneratorProvider) as content.

There is only one caveat which I thought about after creating the issue. That is the location where the generated Assertions classes will be created. The APT needs to run during the compilation of the production code (the domain classes are there), but it needs to output the generated Assertions into the generated test sources. The APT has no control over the location of the generated sources. I think that this would be a problem for:

The main reason why I opened this issue is to have a better integration within IntelliJ and Maven. IntelliJ does it's own build, so in order to update the assertions I have to invoke the plugin manually. Maybe InteliiJ plugin is a better idea :)

joel-costigliola commented 7 years ago

Writing IDE plugins is the way to go, I tried a few years ago to write an eclipse plugin without much success (very little doc for that).

A plugin that would let you select a class or a package and generate assertions for it would be awesome.

I have split the generator from the maven plugin to be able to reuse it from IDEs.

filiphr commented 7 years ago

You are right. IDE plugins are the way to go.

Do you think creating issues for the Eclipse and IntelliJ plugins makes sense? Maybe someone will pick them up if they see them.

I also tried writing an IntelliJ plugin for another project and I am also having problems with the documentation

joel-costigliola commented 7 years ago

IDE plugins should have their own project like the maven plugin, I'm happy to create one here or better in the assertj organization which I plan to migrate all projects to.

For an eclipse plugin I would start from https://github.com/maximeAudrain/jenerate as it allows you to generate code (equals and hashcode).

filiphr commented 7 years ago

IDE plugins should have their own project like the maven plugin, I'm happy to create one here or better in the assertj organization which I plan to migrate all projects to.

Completely agree with you. Each plugin belongs to it's own project. If you have one location where you plan extensions or additional repositories for AssertJ, I would say we create 2 issues in there.

For IntelliJ plugin this are some that are good to start from:

joel-costigliola commented 7 years ago

just created https://github.com/assertj/assertj-assertions-generator-idea-plugin. @filiphr are you interested in taking the lead for the idea plugin project ? (take your time to think about it and feel free to decline)

filiphr commented 7 years ago

@joel-costigliola I can certainly try. The only thing is that I don't have any experience with idea plugin and we'll need some time to make this work. I'll also need to look at the generator code, so I can understand how it interacts.

I can try to do #97 first, I think with that I'll have better understanding of the code.

joel-costigliola commented 7 years ago

Sounds like a good plan @filiphr please take all the time you need, we are all contributing in our spare time so no pressure, really.

joel-costigliola commented 7 years ago

@filiphr I have sent you an invite to be a collaborator of https://github.com/assertj/assertj-assertions-generator-idea-plugin.

KangoV commented 5 years ago

I think we could resurrect this. I've recently been playing around with the Immutables and Mapstruct projects (brilliant stuff), and I think the same process could be used to generate custom assertions without annotating our existing code and would NOT require any IDE plugins.

So for the following model...

public static class TolkienCharacter {
  private String name;
  private int age;
}

we would create our assertions interface with the the new annotation:

@AssertJAssertionsGenerator
public interface MyProject {
  void assertThat(TolkienCharacter actual);
}

which would create the following classes:

public static class TolkienCharacterAssert extends AbstractAssert<TolkienCharacterAssert, TolkienCharacter> {
  public TolkienCharacterAssert(final TolkienCharacter actual) {
    super(actual, TolkienCharacterAssert.class);
  }
  public static TolkienCharacterAssert assertThat(final TolkienCharacter actual) {
    return new TolkienCharacterAssert(actual);
  }
  public TolkienCharacterAssert hasName(final String name) {
    isNotNull();
    if (!Objects.equals(actual.getName(), name)) {
      failWithMessage("Expected character's name to be <%s> but was <%s>", name, actual.getName());
    }
    return this;
  }
  public TolkienCharacterAssert hasAge(final int age) {
    isNotNull();
    if (actual.getAge() != age) {
      failWithMessage("Expected character's age to be <%s> but was <%s>", age, actual.getAge());
    }
    return this;
  }
}
public class MyProjectAssertions {
  public static TolkienCharacterAssert assertThat(final TolkienCharacter actual) {
    return TolkienCharacterAssert.assertThat(actual);
  }
}

I'm still not sure about where the annotation should be placed. You could have a package attribute on the annotation to generate assertions for all classes, e.g.

@Assertions(packageFilter="com.company.model.*") // regex?
public interface MyModel {}

or specifiy the classes as well:

@Assertions(packageFilter="com.company.model.*", classFilter=".*Model") // regex?
public interface MyModel {}

or the classes directly:

@Assertions(classes= { Foo.class, Bar.class })
public interface MyModel {}

This could then be integrated with the Immutables project (maybe?) so that customs assertions could generated when the immutables class are generated.

Thoughts?

joel-costigliola commented 5 years ago

I haven't much experience with annotator processor but here's my 2cents.

I would go with this:

@GenerateAssertJAssertionsFor(""com.company.model.*")
public interface MyProjectAssertions {}

The annotation should be put in a test scope class, we don't want to have any assertj artefacts for the production code.

This could then be integrated with the Immutables project (maybe?) so that customs assertions could generated when the immutables class are generated.

Are you referring to using this for Immutables as a concrete usage ?

KangoV commented 5 years ago

Are you referring to using this for Immutables as a concrete usage ?

Kind of. From my usage of Immutables, it would seem that while the Immutables annotation processor is running it could optionally use the generator in AssertJ to create assertions.

You can see this cooperation happening between Mapstruct and Immutables. If Mapstruct sees Immutables on the path, then it will use the builder generated by immutables when performing mappings.

I believe this is where real innovation can happen :)