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

Consider auto-generating assertThat and subject factory for custom subjects #612

Open
dmitry-timofeev opened this issue Jul 22, 2019 · 7 comments
Labels
P3 not scheduled type=addition A new feature

Comments

@dmitry-timofeev
Copy link

Currently a minimal Truth Subject requires four operations: a constructor, an assertion method, and two static methods for accessing the subject: assertThat and a subject factory (see extension page).

Would it be possible to auto-generate these methods and, possibly, accumulate all custom assertThat overloads in a single place, to cut down the number or operations the developer has to add to a custom subject, and encourage creating custom Subjects even if a single assertion method is needed?

For instance,

@AutoSubject(factoryName = "employees")
public final class EmployeeSubject extends Subject {

  private final Employee actual;

  // The constructor needs to be public if the class with `assertThat`
  // might be outside of its package
  public EmployeeSubject(FailureMetadata failureMetadata, @NullableDecl Employee subject) {
    super(failureMetadata, subject);
    this.actual = subject;
  }

  // User-defined test assertion SPI below this point

  public void hasName(String name) {
    check("name()").that(actual.name()).isEqualTo(name);
  }
}

Will generate these two methods:

  // User-defined entry point
  public static EmployeeSubject assertThat(@NullableDecl Employee employee) {
    return assertAbout(EMPLOYEE_SUBJECT_FACTORY).that(employee);
  }

  // Static method for getting the subject factory (for use with assertAbout())
  public static Subject.Factory<EmployeeSubject, Employee> employees() {
    return EMPLOYEE_SUBJECT_FACTORY;
  }

  // Boiler-plate Subject.Factory for EmployeeSubject
  private static final Subject.Factory<EmployeeSubject, Employee> EMPLOYEE_SUBJECT_FACTORY =
      EmployeeSubject::new;

As a new user of Truth, I can't say if such implementations are common enough so that it makes sense to generate them. There are also other considerations:

  1. Visibility — if there are subjects for package-private types in several packages, the Truth won't be able to produce a single file with all assertions. What shall it produce in this case — a file per subject, or per package?
  2. Discoverability — if Truth produces a single file with all assertions (which might not always work ^), it is trivial to import it knowing its name. But if it generates multiple files, it might be more difficult for the end-user to discover them.

#251, which suggested generating the factory, is likely related.

@talios
Copy link

talios commented Jul 23, 2019

I'd be down with a generator if it generated assertThatEmployee in the above - one thing I don't (overly) like is multiple static imports of assertThat which has bitten me in the past with other assertion libraries. Mostly in cases where subject class also implemented Iterable and things would get.... confusing depending on import order.

@cpovirk
Copy link
Member

cpovirk commented Jul 24, 2019

Thanks. This is something that we've given a little thought to in the past. I'll try to summarize here.

The boilerplate is definitely unfortunate. While you can cut the 2 methods down to this...

  public static EmployeeSubject assertThat(Employee employee) {
    return assertAbout(employees()).that(employee);
  }

  public static Factory<EmployeeSubject, Employee> employees() {
    return EmployeeSubject::new;
  }

...so that there's no need for EMPLOYEE_SUBJECT_FACTORY, that's still a bunch of boilerplate (on top of the constructor and field, plus the actual assertion methods, as you note above).

On top of that, there's the possibility that we could generate the assertion methods themselves in straightforward cases (like the name case above).

For those reasons, I do think we will look into this eventually.

I say only "eventually," though, for a few reasons:

  • First, we threw some extra effort into Truth to release 1.0, but now we want to let things settle for a little to get a better idea of what the Next Big Thing will be for Truth -- possibly including features like this.
  • If we decide to take the more ambitious route of generating assertion methods, too, then there are a lot more design questions that we'll have to resolve. (You mentioned a couple design questions already, as did @talios.)
  • Finally, there are some specific technical issues around annotation processors (which seems to be the approach you have in mind here?) that generate public API. The likely outcome of that is that any autogenerator we provided would be more of an "old-fashioned" code generator. (Such a generator could still be wired up as part of a build for users who want that, but it might be trickier.)

A little more on annotation processors that generate public API, cribbed from some internal documentation on the topic:

  • Users may be surprised when they don't see assertThat or employees defined in the source.
  • IDEs' incremental compilation often has a harder time with annotation processors. Similarly, build systems may need to fall back to a slow path that includes running annotation processors and forcing all dependent targets to wait to build.
  • Refactorings can be harder.
  • Generated code is usually harder to document. (To be fair, there's usually little to write about methods like assertThat, and a code generator might do a better job of filling in a little boilerplate doc than a human would.)

@astubbs
Copy link

astubbs commented Jul 28, 2021

I'd be down with a generator if it generated assertThatEmployee in the above - one thing I don't (overly) like is multiple static imports of assertThat which has bitten me in the past with other assertion libraries. Mostly in cases where subject class also implemented Iterable and things would get.... confusing depending on import order.

Oh man - hey dude! :) I’m keen to look into this too… perhaps we can host some truth extensions somewhere to get something prototyped..

@astubbs
Copy link

astubbs commented Jul 29, 2021

Ok @talios here ya go buddy :) #894

Super keen to get your input / support on it.

If it's totally not feasible to get this merged any time soon - that's fine, we can decide some other structure to maintain / host it. Or just keep it as a long running fork - still might be easier 🤷

@astubbs
Copy link

astubbs commented Nov 3, 2021

As per the PR, this is now available here: https://github.com/astubbs/truth-generator as a pre-release SNAPSHOT that can be installed locally. Will work on getting it into repo1 soon.

@astubbs
Copy link

astubbs commented Nov 4, 2021

The plugin is now available in a couple of Maven repositories (but not repo1 yet), and the README has been updated with feature list and examples. See the README for details.

@astubbs
Copy link

astubbs commented May 6, 2022

FYI 0.1 of the library / plugin is now publicly available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P3 not scheduled type=addition A new feature
Projects
None yet
Development

No branches or pull requests

5 participants