0

In a certain unit test for my class, I want to assert that an assertion has been written for all member fields. That is, given

public class Foo {
  int a;
  int b;
  String c;
}

I want to do something like

@Test
public void testFieldsAgainstExternalModel(Foo model, Bar externalModel) {
  assertEquals(model.a, externalModel.getA());
  assertEquals(model.b, externalModel.getSomethingNamedDifferently());
  assertEquals(model.c, externalModel.getC().toString());
  assertNumberOfAssertionsInThisTest(3); // <-- How do I do this?
}

Of course, counting the number of assertions doesn't ensure that they had anything to do with each field having an assertion. But, I'm just trying to write a test that fails if a developer adds a field to Foo and forgets to update this test.

  • Naivest approach: The simplest thing I can think of is to use reflection to assert the number of fields Foo has, e.g. assertEquals(3, Foo.getDeclaredFields().count()). But, I can easily see a programmer bumping up the number without actually adding an assertion.

  • Still a naive approach, but acceptable: I think if I can at least count the number of assertions, it would sufficiently guide programmers toward the intended need.

  • More exact approaches: Of course it seems to best approach is to actual keep a table of which fields have appeared in the assertion (e.g. through a wrapper method like assertEqualsWithCounters), but in reality my situation's a little more complicated. For example, my model is actually a generated class (@AutoMatter) and I'll be using getters on both sides of the assertion, so it'll be hard to infer truly which field has been "touched" by an assertion. That's why just counting assertions and remaining agnostic to actual types and counts, would be easiest for me.

Andrew Cheong
  • 29,362
  • 15
  • 90
  • 145
  • A much simpler approach is (1) use Lombok to cause your class to have a generated toString, so you know it includes all member variables. (2) just compare the results of toString. – Zag Jan 07 '19 at 14:03
  • @Zag But relying on parsing toString() output is extremely bad practice. And what if that class has a toString() already, or some have? – GhostCat Jan 07 '19 at 19:20
  • Use [OpenPOJO](https://github.com/OpenPojo/openpojo). – Boris the Spider Jan 07 '19 at 19:56

3 Answers3

3

But, I'm just trying to write a test that fails if a developer adds a field to Foo and forgets to update this test.

My company uses a mapping framework to map beans from the db, fields are one to one with the database, to a model bean we return to the client that we can modify as needed. I believe this is a common use case and essentially what you are trying to get at. You should be less concerned with testing changes to Foo, and more concerned with ensuring nothing breaks with the mapping from Foo to Bar.

Class BeanDb{
 private int a;
 private int b;
 ...
}

Class Bean{
 private int a;
 private int b;
 private String somethingElse;
 ...
}

Then we have have a proivder test that uses isEqualToComparingFieldByFieldRecursively to compare the two beans.

private Bean bean = new Bean();
private Bean beanDb = new BeanDb();
private BeanProvider provider = new BeanProvider();

@Before
public void setup(){
 bean.setA(1);
 bean.setB(2);

 beanDb.setA(1);
 beanDb.setB(2);
}

@Test
public void prepareForDatabase(){
    BeanDb db = provider.prepareForDatabase(bean);

    assertThat(beanDb).isEqualToComparingFieldByFieldRecursively(db);
}

@Test
public void populateFromDatabase(){
    Bean model = provider.populateFromDatabase(beanDb);

    assertThat(model).isEqualToComparingFieldByFieldRecursively(bean);
}

This actually catches a lot of bugs. If something changes in the model bean / db bean the test will break in our continuous integrations suite. The caveat here is that isEqualToComparingFieldByFieldRecursively will not catch newly added fields. When I review code / pull requests and BeanDb or the provider has been updated I ask, "Did you update the provider test?"

Phil Ninan
  • 1,108
  • 1
  • 14
  • 23
  • I like your answer very much, so I also added my variation of it ;-) – GhostCat Jan 07 '19 at 19:34
  • Hm, the adding of new fields was one of the primary reasons I wanted an automatic catch in my case... but thanks, I've been keeping mappers in mind since your suggestion. – Andrew Cheong Jan 14 '19 at 21:47
2

I think you may need to use PowerMockito as Mockito does not support mocking static methods.

I haven't tested the code below - but my idea would be to do the following:

@RunWith(PowerMockRunner.class)
public class Tester {
    public class Foo {
        int a = 5;
        int b = 6;
        String c = "7";
    }
    class Bar {

        public int getA() {
            return 5;
        }

        public int getSomethingNamedDifferently() {
            return 6;
        }

        public Integer getC() {
            return 7;
        }
    }

    @Test
    public void testFieldsAgainstExternalModel() {
        testFieldsAgainstExternalModel(new Foo(), new Bar());
    }
    public void testFieldsAgainstExternalModel(Foo model, Bar externalModel) {
        Assert spiedAssert = PowerMockito.spy(Assert.class);

        spiedAssert.assertEquals(model.a, externalModel.getA());
        spiedAssert.assertEquals(model.b, externalModel.getSomethingNamedDifferently());
        spiedAssert.assertEquals(model.c, externalModel.getC().toString());
        PowerMockito.verify(spiedAssert, times(3)).assertEquals(any(), any());
    }
}
Community
  • 1
  • 1
munyengm
  • 15,029
  • 4
  • 24
  • 34
  • A nice idea, but honestly, rather a dirty workaround. First of all, that static mocking can quickly lead to all kinds of problems. For example there are a bunch of rules you need to follow, like you need a PrepareForTest annotation. Which is missing from your example. So I am wondering if your code really works. I am also wondering if those spied on asserts ... are really doing an asserts check. Have you verified that they **fire** when the assert doesn't hold? – GhostCat Jan 07 '19 at 19:24
1

I think that the answer to

assertNumberOfAssertionsInThisTest(3); // <-- How do I do this?

is: you do not do that. This test is close to useless.

If there are only 3 fields, you can see with your eyes that you are checking those three fields. You are probably thinking about 15 fields in that class, but then:

assertEquals(model.a, externalModel.getA());
assertEquals(model.b, externalModel.getSomethingNamedDifferently());
assertEquals(model.c, externalModel.getC().toString());
assertEquals(model.c, externalModel.getC().toString());
assertEquals(model.d, externalModel.getD().toString());

... assertEquals(model.o, externalModel.getO().toString());

assertNumberOfAssertionsInThisTest(15); 

would not help you. Guess what: when pulling together my faked example, I omitted property 14 (model.n), but instead I had a copy/paste error and checked model.c two times, leading to an overall count of 15.

Therefore I agree with user Phil, just suggesting yet another approach, like this:

  • ensure that the bean classes have reasonable equals() (and maybe hashCode() methods). Either by using Lombok, or by having a base bean class that makes use of an EqualsBuilder (we actually do the later in our projects)
  • then have test code either use a faked bean and simply compare if that faked bean is equal to the bean that your production code created from a (maybe faked) external model object.
GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • Ah, sorry. See, where I was building to was doing something like this: `assertEquals(3, MyBaseClass.class.getDeclaredFields().length);`. So if fields are added to the base class, the assertion _would_ fail, hinting to the programmer that something needs to be done. But I agree this is hardly the best design. – Andrew Cheong Jan 10 '19 at 15:59
  • @AndrewCheong As said: for 3 fields, you can see that. When you have 15 fields, you can get the number right, but still only test 14 fields easily. – GhostCat Jan 10 '19 at 16:02