The correct way to unit test class with inner class

Class A has an internal class B. Class A has a private list of objects of class B, which it provides through the getBs, addB, and removeB methods. How to unit test removeB method?

I was hoping to create two equal class B layouts, add each, and then remove one of them twice (the result is to remove both). However, since then I found out due to an error that the equals method will not be called on the mock objects.

Was it stupid (or impossible) to try to isolate the outer class from its inner class for unit testing?


Sample code follows

Class to check:

import java.util.ArrayList; import java.util.List; public class Example { private List<Inner> inners = new ArrayList<Inner>(); public List<Inner> getInners() { return inners; } public void addInner(Inner i) { inners.add(i); } public void removeInner(Inner i) { inners.remove(i); } /** * equalityField represents all fields that are used for testing equality */ public class Inner { private int equalityField; private int otherFields; public int getEqualityField() { return equalityField; } public Inner(int field) { this.equalityField = field; } @Override public boolean equals(Object o) { if (o == null) return false; if (o.getClass() != this.getClass()) return false; Inner other = (Inner) o; if (equalityField == other.getEqualityField()) return true; return false; } } } 

Test case that didn't turn out so wonderful:

 import static org.junit.Assert.*; import org.junit.Test; import org.easymock.classextension.EasyMock; public class ExampleTest { @Test public void testRemoveInner() { Example.Inner[] mockInner = new Example.Inner[2]; mockInner[0] = EasyMock.createMock(Example.Inner.class); mockInner[1] = EasyMock.createMock(Example.Inner.class); Example e = new Example(); e.addInner(mockInner[0]); e.addInner(mockInner[1]); e.removeInner(mockInner[0]); e.removeInner(mockInner[0]); assertEquals(0, e.getInners().size()); } } 
+4
source share
4 answers

Why should you mock the inner class? If I ran into this problem, I would just use the class as is and verify that the behavior of the outer class works as expected. You do not need to mock the inner class to do this.

Aside: when overriding the equals () method, we also recommend overriding the hashCode () method.

+4
source

First, the answer to your question: Yes, it is usually a bad idea to try and separate the inner and outer class in unit testing. This is usually due to the fact that they are closely related to each other, for example, Inner makes sense only in the context of Outer, or Outer has a factory method that returns an implementation of the Inner interface. If the two are not related to each other, split them into two files. It makes your test life easier.

Secondly (using the above code as an example), you really don't need to make fun of the above code. Just create some instances and leave. It looks like you have enough to work. You can always do something like:

 public void testRemoveInner() { Example.Inner[] inner = new Example.Inner(45); Example e = new Example(); e.addInner(inner); e.addInner(inner); e.removeInner(inner); assertEquals(0, e.getInners().size()); } 

No necessary changes.

Third, try and find out what you are actually checking. In the code above, you verify that if I add something to the list, I can delete it. By the way, you are saying that if there are several objects that are β€œequal”; the above code does not do this, from the definition of Collection # remove () :

Removes one instance of the specified item from this kit, if any (sold separately). More formally, deletes an element e such that (o == null? E == null: o.equals (e)) if this collection contains one or more of these elements.

Is this really what you want to check?

Fourth, if you implement equals, also implement hashCode (see Overriding Equals and hashCode in Java ).

+5
source

Like the TDD newb, which first encountered this situation for the first time, I found that as a result of refactoring I got "unchecked" inner classes, and if I use the "clean" TDD approach, I wonder if it can end up with inner classes in any other way .

The problem is that assuming that one or more references are made from an inner class for an external class object, this refactoring often interrupts one or more tests. The reason for this is quite simple: your layout object, if spy , is actually a wrapper around a real object

 MyClass myClass = spy( new MyClass() ); 

... but inner classes will always refer to the real object, so it often happens that trying to apply mocks to myClass will not work. Worse, even without bullying, there is a high probability that this thing will completely and inexplicably fail in its normal business. Also keep in mind that your spy will not run the real constructor method for itself: many errors.

Considering that our development of our tests is an investment in quality, it seems to me that it would be terribly embarrassing to just say: "Well, I'm just going to refuse this test."

I offer two options:

  • if you replace your inner class with direct access to the outer fields of the class using getter / setter methods (which may be private , rather strange), this will mean that these are mock methods that will be used ... and therefore false fields. Then your existing tests continue.

  • Another possibility is to reorganize this inner class to make it a free-standing class, an instance of which replaces your inner class and transfers one or more test methods to a new test class for this new class. Then you will encounter the (simple) task of fitting things so that references to the external object of the class are parameterized (that is, 99% of the cases are passed as a constructor parameter) and then can be mocked accordingly. It should not be too complicated. Although you may need to add the appropriate getter / setter methods for private fields in the outer class and / or make one or more of its private package-private methods. From now on, the inner class becomes a black box for testing the outer class.

Using any approach, you have not lost quality.

+1
source

You can extend the class under test (A) with the made ATest class, which offers a public method that allows you to peek into private list B

0
source

Source: https://habr.com/ru/post/1382940/


All Articles