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

Using annotation processor #96

Open
filiphr opened this issue May 19, 2017 · 13 comments
Open

Using annotation processor #96

filiphr opened this issue May 19, 2017 · 13 comments

Comments

@filiphr
Copy link

filiphr commented May 19, 2017

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
Copy link
Member

So you would annotate domain classes ?

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

@filiphr
Copy link
Author

filiphr commented May 19, 2017

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:

  • IDEs (at least IntelliJ) - you can only configure the location for the production and test generated sources (the APT goes over the production classes).
  • maven-compiler-plugin - If there is a way to pass the location of the generated sources to the maven compiler you can theoretically define one compilation that will output to different location. However, this won't make a big difference to what is there with the AssertJ maven plugin. On top of that I am not sure that the IDEs (at least InteliiJ) will be able to pick up the more complex setup of the plugin.

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
Copy link
Member

joel-costigliola commented May 20, 2017

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
Copy link
Author

filiphr commented May 20, 2017

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
Copy link
Member

joel-costigliola commented May 20, 2017

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
Copy link
Author

filiphr commented May 20, 2017

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
Copy link
Member

joel-costigliola commented May 20, 2017

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
Copy link
Author

filiphr commented May 20, 2017

@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
Copy link
Member

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
Copy link
Member

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

@KangoV
Copy link

KangoV commented Sep 18, 2019

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
Copy link
Member

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
Copy link

KangoV commented Sep 20, 2019

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants