Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to unit test class with inner class

Class A has inner class B. Class A has a private list of class B objects that it makes available through getBs, addB, and removeB methods. How do I unit test the removeB method?

I was hoping to create two equal mocks of class B, add each, and then remove one of them twice (result being to remove both). However, I've since learned through failure that the equals method will not be called on mock objects.

Was it foolish (or impossible) to try and isolate an outer class from its inner class for unit testing?


Sample code follows

Class to test:

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 work out so great:

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());
    }
}
like image 489
Matt Avatar asked Nov 24 '11 18:11

Matt


2 Answers

Why do you have to mock you inner class? If I was faced with this issue I would just use the class as is and test that the behaviour of the outer class works as expected. You don't need to mock the inner class to do that.

As an aside: when overriding the equals() method it is advisable to also override the hashCode() method, too.

like image 163
Mark Avatar answered Sep 30 '22 12:09

Mark


First, the answer to your question: Yes, it is generally a bad idea to try and separate an inner and outer class when unit testing. Usually, this is because the two are intimately linked, for instance Inner only makes sense in the context of Outer, or Outer has a factory method which returns an implementation of an interface, an Inner. If the two aren't really linked, then separate them out into two files. It makes your testing life easier.

Secondly, (using the above code as an example), you don't actually need to mock the above code. Just create some instances and away you go. It looks like you have enough to work with. 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 mocks necessary.

Thirdly, try and work out what you are actually testing. In the above code, you're testing that if I add something to a list, then I can remove it. By the way, you state that if there are multiple objects which are 'equal'; the above code doesn't do that, from the definition of Collection#remove():

Removes a single instance of the specified element from this collection, if it is present (optional operation). More formally, removes an element e such that (o==null ? e==null : o.equals(e)), if this collection contains one or more such elements.

Is that really what you want to test?

Fourthly, if you implement equals, implement hashCode as well (see Overriding equals and hashCode in Java).

like image 33
Matthew Farwell Avatar answered Sep 30 '22 10:09

Matthew Farwell