Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test constructors

I have a class I am adding unit tests to. The class has several constructors which take different types and converts them into a canonical form, which can then be converted into other types.

public class Money {     public Money(long l) {         this.value = l;     }      public Money(String s) {         this.value = toLong(s);     }      public long getLong() {         return this.value;     }      public String getString() {         return toString(this.value);     } } 

In reality there are a couple of other types it accepts and converts to.

I am trying to work out what the most appropriate way to test these constructors is.

Should there be a test per-constructor and output type:

@Test public void longConstructor_getLong_MatchesValuePassedToConstructor() {     final long value = 1.00l;      Money m = new Money(value);     long result = m.getLong();      assertEquals(value, result); } 

This leads to a lot of different tests. As you can see, I'm struggling to name them.

Should there be multiple asserts:

@Test public void longConstructor_outputsMatchValuePassedToConstructor() {     final long longValue = 1.00l;     final String stringResult = "1.00";      Money m = new Money(longValue);      assertEquals(longValue, m.getLong());     assertEquals(stringResult, m.getString()); } 

This has multiple asserts, which makes me uncomfortable. It is also testing getString (and by proxy toString) but not stating that in the test name. Naming these are even harder.

Am I going about it completely wrong by focussing on the constructors. Should I just test the conversion methods? But then the following test will miss the toLong method.

@Test public void getString_MatchesValuePassedToConstructor() {     final long value = 1.00;     final String expectedResult = "1.00";      Money m = new Money(value);     String result = m.getLong();     assertEquals(expectedResult, result); } 

This is a legacy class and I can't change the original class.

like image 623
ICR Avatar asked May 26 '11 12:05

ICR


People also ask

How do you test a constructor in unit testing?

To test that a constructor does its job (of making the class invariant true), you have to first use the constructor in creating a new object and then test that every field of the object has the correct value. Yes, you need need an assertEquals call for each field.

Should we unit test constructors?

Is it worth testing that? Yes, it is often worth testing constructors with their own complex behavior.

Can JUnit test have constructor?

JUnit provides lot of methods to test our cases. We can test the constructor of a class using the same techniques we have used in our previous examples. Sometimes we need to initialize the objects and we do them in a constructor. In JUnit we can also do same using the @Before method.

How do you call a constructor in JUnit?

@Before allows overriding parent class behavior, constructors force you to call parent class constructors. The constructor runs before subclass constructors and @Rule methods, @Before runs after all of those. Exceptions during @Before cause @After methods to be called, Exceptions in constructor don't.


2 Answers

It looks like you've got a canonical way of getting the "raw" value (toLong in this case) - so just test that all the constructors are correct when you fetch that value. Then you can test other methods (such as getString()) based on a single constructor, as you know that once the various constructors have finished, they all leave the object in the same state.

This is assuming somewhat white-box testing - i.e. you know that toLong is really a simple reflection of the internal state, so it's okay to test that + a constructor in a test.

like image 168
Jon Skeet Avatar answered Oct 02 '22 17:10

Jon Skeet


The expected result from a constructor test is: an instance has been created

Following this idea you could limit the work in constructor tests to pure instantiation:

@Test public void testMoneyString() {     try {       new Money("0");       new Money("10.0");       new Money("-10.0");     } catch (Exception e) {       fail(e.getMessage());     } }  @Test public void testMoneyStringIllegalValue() {     try {       new Money(null);       fail("Exception was expected for null input");     } catch (IllegalArgumentException e) {               }      try {       new Money("");       fail("Exception was expected for empty input");     } catch (IllegalArgumentException e) {               }      try {       new Money("abc");       fail("Exception was expected for non-number input");     } catch (IllegalArgumentException e) {               }  } 

A test to verify if the conversions work could be assigned to the getters.

like image 25
Andreas Dolk Avatar answered Oct 02 '22 17:10

Andreas Dolk