Protobuf Extension

ProtoTruth is an extension of the Truth library which lets you make assertions about Protobufs, a rich data interchange format. To get started, follow the steps below, or for Lite proto follow our Lite Proto instructions:

  1. Add the appropriate dependency to your build:

    Maven:

    <dependency>
      <groupId>com.google.truth.extensions</groupId>
      <artifactId>truth-proto-extension</artifactId>
      <version>1.4.4</version>
    </dependency>
    

    Gradle:

    repositories {
      mavenCentral()
    }
    dependencies {
      testImplementation "com.google.truth.extensions:truth-proto-extension:1.4.4"
    }
    

    Use truth-liteproto-extension if you are using the lite version of Protobufs. Note that this extension lacks many of the features in the full extension due to the lack of runtime descriptors. You’ll also need to import LiteProtoTruth instead of ProtoTruth.

  2. Add a static import:

    import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
    

    NOTE: ProtoTruth.assertThat and Truth.assertThat can both be statically imported in the same file. Java handles overloaded static imports much like overloads defined in the same class.

  3. Assert away!

    MyProto expected = MyProto.newBuilder().addBar(3).addBar(5).setFoo("qux").build();
    MyProto actual =   MyProto.newBuilder().addBar(5).addBar(3).setFoo("quizzux").build();
    assertThat(actual).ignoringRepeatedFieldOrder().isEqualTo(expected);
    

    This assertion yields the following failure message:

    Not true that messages compare equal.  Differences were found:
    modified: foo: "qux" -> "quizzux"
    

Supported Use Cases

Support exists for:

  • Assertions on singular, generic Proto 2 messages.
  • Assertions on singular, generic Proto 3 messages.
  • Assertions on Iterables, Maps, and Multimaps of such messages.
  • Tailored assertions for the Any type, for Proto 3.
  • Lite protos, though support is nominal

Support does not currently exist (yet) for:

  • Tailored assertions for extension fields for Proto 2.
  • Tailored assertions for the UnknownFieldSet.
  • Tailored assertions for utility protos, including FieldMasks.

Support will not exist for:

  • gRPC, for which Truth is not an appropriate home. Mocking libraries are better suited to testing RPC interactions.

Strict default behavior

By default, the extension is fairly strict and requires you to explicitly state your test assumptions. It will:

  • not ignore field absence; to change this behavior, use: ignoringFieldAbsence()
  • not ignore repeated field order; to change this behavior, use: ignoringRepeatedFieldOrder()
  • not report mismatches only; to change this behavior, use: reportingMismatchesOnly()
  • use exact equality for floating-point fields; to change this behavior, use: usingDoubleTolerance and/or usingFloatTolerance
  • check all fields; to change this behavior, use a combination of:
    • ignoringFields(int...)
    • ignoringFieldDescriptors(FieldDescriptor...)
    • withPartialScope(FieldScope)
    • comparingExpectedFieldsOnly()
    • ignoringFieldScope(FieldScope)

In summary, the default isEqualTo() behavior of ProtoSubject is behaviorally identical to Subject behavior. However, you will get much better error messaging in the event of a failure.

Getting better failure messages by enabling pairing of Iterable elements

When an assertion involving an Iterable of protos fails, the failure messages can often be hard to understand. If you know of some key function which uniquely indexes the expected protos, you can use the displayingDiffsPairedBy method to tell ProtoTruth about it. For example, if you have a proto called MyRecord, and you’re making an assertion about an Iterable<MyRecord>, and the expected records have unique values of an id field, then you could write this:

assertThat(actualRecordProtos)
    .displayingDiffsPairedBy(MyRecord::getId)
    .containsExactlyElementsIn(expectedRecords);

If this assertion fails, the failure message will pair up any missing and unexpected elements by their IDs. For example, it might tell you that the actual Iterable was missing an element with ID 2, that it had an unexpected element with ID 3, or that the element with ID 4 wasn’t equivalent to the one it expected… and in this last case, it will also show a field-by-field diff between the proto it got and the one it expected.

(If an assertion about a Map fails, the failure message will automatically match up any missing and unexpected entries using their keys. You can think of the displayingDiffsPairedBy method as providing an equivalent for an assertion about an Iterable. Note that this won’t affect whether the test passes or fails.)

Lite Proto and Android Support

Nominal support exists for Lite protos, commonly used in Android code due to the size of the full-proto runtime. However, most features available for full protos are not available for lite protos because runtime descriptors are essential for doing dynamic comparisons. To use lite protos with proto truth:

  1. Add a dependency:

    Maven:

    <dependency>
      <groupId>com.google.truth.extensions</groupId>
      <artifactId>truth-liteproto-extension</artifactId>
      <version>1.4.4</version>
    </dependency>
    

    Gradle:

    repositories {
      mavenCentral()
    }
    dependencies {
      testImplementation "com.google.truth.extensions:truth-liteproto-extension:1.4.4"
    }
    
  2. Add a static import:

    import static com.google.common.truth.extensions.proto.LiteProtoTruth.assertThat;
    
  3. And assert away!