Search code examples
javagenericscollectionsjunitbounded-wildcard

How to test if a generic list contains the exact subcollection of a subtype in Java?


I have an abstract class, AbstractService, and several classes which extend this abstract class:

Silly class diagram

I then have a ServiceFactory that returns me a generic list with some services, according to a parameter I pass:

public class ServiceFactory {
    public List<? extends AbstractService> getServices(final MyParameter param) {
        // Service is an interface implemented by AbstractService
        List<Service> services = new ArrayList<>();

        for (Foo foo : param.getFoos()) {
            services.add(new AService(foo.getBar()));
        }
        // creates the rest of the services
        return services;
    }
}

In my UnitTest, I'd like to verify if my list of services contains exactly 3 AService subtypes. The way I'm doing that now is:

@Test
public void serviceFactoryShouldReturnAServiceForEachFoo() {
  // I'm mocking MyParameter and Foo here
  Mockito.when(param.getFoos()).thenReturn(Arrays.asList(foo, foo, foo);

  AService aservice = new AService(foo);

  List<AService> expectedServices = Arrays.asList(aservice, aservice, aservice);
  List<? extends AbstractService> actualServices = serviceFactory.getServices();

  assertTrue(CollectionUtils.isSubCollection(expectedServices, actualServices));
}

When actualServices contains less than 3 Aservice, the test fails correctly. The only problem with this solution is that if actualServices contains more than 3 AService, the test passes...

Is there a method that does that or should I implement it myself using loops?


Solution

  • You could use hamcrest Matchers.

    hamcrest-library contains matchers to check collection/iterable contents.

    I hope the following sample matches your szenario loosely

    import static org.hamcrest.CoreMatchers.not;
    import static org.hamcrest.MatcherAssert.assertThat;
    import static org.mockito.Mockito.mock;
    import static org.hamcrest.Matchers.containsInAnyOrder;
    
    import java.util.ArrayList;
    import java.util.Collection;
    
    import org.apache.commons.lang.builder.EqualsBuilder;
    
    import org.junit.Test;
    
    public class ServiceFactoryTest
    {
        @Test
        public void serviceFactoryShouldReturnAServiceForEachFoo()
        {
            Foo foo = mock( Foo.class );
            Service service = new AService( foo );
            Service[] expected = { service, service, service };
            Service[] tooFew = { service, service };
            Service[] tooMany = { service, service, service, service };
    
            ServiceFactory factory = new ServiceFactory();
    
            assertThat( factory.createServices( foo, foo, foo ), containsInAnyOrder( expected ) );
            assertThat( factory.createServices( foo, foo, foo ), not( containsInAnyOrder( tooFew ) ) );
            assertThat( factory.createServices( foo, foo, foo ), not( containsInAnyOrder( tooMany ) ) );
        }
    
        interface Foo
        {}
    
        interface Service
        {}
    
        class AService implements Service
        {
            Foo foo;
    
            public AService( Foo foo )
            {
                this.foo = foo;
            }
    
            @Override
            public boolean equals( Object that )
            {
                return EqualsBuilder.reflectionEquals( this, that );
            }
        }
    
        class ServiceFactory
        {
            Collection<? extends Service> createServices( Foo... foos )
            {
                Collection<Service> list = new ArrayList<>();
    
                for ( Foo foo : foos )
                {
                    list.add( new AService( foo ) );
                }
    
                return list;
            }
        }
    }