2

Java 11, New to TDD and to unit testing in general. I have a class that contains a unique method : buy() It does a few thing like search for the product in the productRepo, prepare the receipt. It finally returns an object (receipt).

public Receipt buy(){
   // find product
   // create the receipt
   return receipt; 
}

NOTE : I omitted the parameters because they are not important to the problem

Now I have to do the unit tests for that method. I have a few problems : I would normally run a test and compare the expectedReceipt with the one obtained from the call. However :

  1. the Receipt class has no getters (only a constructor)
  2. theReceipt class doesnt override the equals method and doesnt only have simple attributes (double, string ,etc)
  3. I cant include any new dependency to my project, I got : mockito and junit.
  4. The method buy() has to absolutely return an instance of Receipt.

So I have no way of testing for equality. What can I do ? Heres what I tried :

  1. I verified that some method was called from my mock (productRepository).
  2. I tried extending that Receipt class and implement myself an equals method. However, the generic equals method from the Object class seems to be called in my tests.

Any ideas or hints ? Thank you

SightBack
  • 71
  • 1
  • 7
  • 1
    Putting aside the question of testing for a minute - in a real scenario, how is one supposed to used the `Receipt` class with no getters? Are the attributes public? – Mureinik Sep 13 '20 at 02:27
  • I wont lie this is school work, and I cannot understand why one would force the usage of a POJO without getters and private members (nope theyre not public). – SightBack Sep 13 '20 at 02:37
  • One way to test this might be to use reflection to access the private members of `Receipt`. I'm also having trouble seeing how this is a useful homework assignment. Could you post the full code for `Receipt`? – markspace Sep 13 '20 at 02:43

1 Answers1

0

This question highlights an import (and IMHO, often overlooked) aspect of [unit] tests. Their purpose isn't to only assert the correct behavior of a piece of software (a unit), but to serve as an executable form of documentation, and to show how this piece of software is supposed to be used.

The fact that you're struggling to test this method highlights the fact that a "real" user of this method would struggle to use it - they would call this method and get back an object they can't really use. In a real world scenario, if you were faced with such a problem, chances are you wouldn't try to bend over backwards to write a test, but go back and fix the Receipt class to make it easier (possible?) to use.

Having said that, since this is a school assignment and you can't change the method/class' signature, there are a few possible approaches here:

  1. You omitted the arguments buy gets. It may be a poor design (as is returning an object with no getters, TBH), but I'd double check if any of these arguments are modified by calling the method. If so, you could assert that they are modified in the desired way.

  2. As you mentioned, you could mock the external services (e.g., the productRepo) the method uses, and verify they are called with the correct arguments.

  3. You could use reflection to access the private fields of the returned Receipt object, and assert their values (see, e.g., How to read the value of a private field from a different class in Java? for details). This practice is usually frowned upon as it breaks the principle of encapsulation, and may produce a very fragile test. Then again, if the only option that works is a bad option, maybe it isn't that bad.

  4. Negative testing - you didn't share any information about how the method behaves if you call it with invalid input (e.g., a product that doesn't exist or is out of stock). Does it throw an exception? Does it return null? These behaviors can and should be tested.

Mureinik
  • 297,002
  • 52
  • 306
  • 350
  • The arguments passed are two strings that are not modified sadly. I will probably use reflection for this specific case to test thoroughly this method. As to the scenario when the buy() method throws an exception(only 1 possible case), what is expected for unit testing : - 1 test that checks if the exception is thrown - Or 2 tests, one for the case above and one for when the exception isnt thrown in a valid buying context ? ie How explicit do I have to be with the tests? Thanks! – SightBack Sep 14 '20 at 13:47
  • @123dz If you want to be thorough - you need one positive test for each "representative" set of arguments that you can assert the response one (in this case - using reflection, as you stated you're going to do). You also need one negative test for each "representative" set of invalid arguments (e.g., "s1 is ok and s2 isn't", "s1 is not ok but s2 is", "both are not ok" etc). For each one of these, you need to assert the appropriate exception is thrown. – Mureinik Sep 14 '20 at 13:57