Mocking Java enum para agregar un valor para probar el caso fallido

Tengo un conmutador de enumeración más o menos así:

public static enum MyEnum {A, B} public int foo(MyEnum value) { switch(value) { case(A): return calculateSomething(); case(B): return calculateSomethingElse(); } throw new IllegalArgumentException("Do not know how to handle " + value); } 

y me gustaría tener todas las líneas cubiertas por las pruebas, pero como se espera que el código maneje todas las posibilidades, no puedo proporcionar un valor sin su statement de caso correspondiente en el cambio.

No es posible extender la enumeración para agregar un valor extra, y solo burlarse del método equals para devolver false tampoco funcionará porque el bytecode generado utiliza una tabla de salto detrás de las cortinas para ir al caso correcto … Así que he pensó que tal vez se podría lograr algo de magia negra con PowerMock o algo así.

¡Gracias!

editar :

Como soy el propietario de la enumeración, he pensado que podría simplemente agregar un método a los valores y así evitar el problema del interruptor por completo; pero dejo la pregunta ya que aún es interesante.

Aquí hay un ejemplo completo.

El código es casi como el original (solo mejoró la validación de la prueba):

 public enum MyEnum {A, B} public class Bar { public int foo(MyEnum value) { switch (value) { case A: return 1; case B: return 2; } throw new IllegalArgumentException("Do not know how to handle " + value); } } 

Y aquí está la prueba unitaria con cobertura de código completo, la prueba funciona con Powermock (1.4.10), Mockito (1.8.5) y JUnit (4.8.2):

 @RunWith(PowerMockRunner.class) public class BarTest { private Bar bar; @Before public void createBar() { bar = new Bar(); } @Test(expected = IllegalArgumentException.class) @PrepareForTest(MyEnum.class) public void unknownValueShouldThrowException() throws Exception { MyEnum C = PowerMockito.mock(MyEnum.class); Whitebox.setInternalState(C, "name", "C"); Whitebox.setInternalState(C, "ordinal", 2); PowerMockito.mockStatic(MyEnum.class); PowerMockito.when(MyEnum.values()).thenReturn(new MyEnum[]{MyEnum.A, MyEnum.B, C}); bar.foo(C); } @Test public void AShouldReturn1() { assertEquals(1, bar.foo(MyEnum.A)); } @Test public void BShouldReturn2() { assertEquals(2, bar.foo(MyEnum.B)); } } 

Resultado:

 Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.628 sec 

@Melloware

… código que ejecuta la sentencia switch () Java arroja un java.lang.ArrayIndexOutOfBounds …

Yo tengo este mismo problema. Ejecute su prueba con el nuevo Enum como el primero en su clase de prueba. Creé un error con este problema: https://code.google.com/p/powermock/issues/detail?id=440

En lugar de utilizar una manipulación radical de bytecode para permitir que una prueba llegue a la última línea en foo , la eliminaría y confiaría en el análisis de código estático. Por ejemplo, IntelliJ IDEA tiene la inspección del código “Enum switch statement that missed case”, que produciría una advertencia para el método foo si no tuviera un case .

Como indicó en su edición, puede agregar la funcionalidad en la enumeración misma. Sin embargo, esta podría no ser la mejor opción, ya que puede violar el principio de “Una Responsabilidad”. Otra forma de lograr esto es crear un mapa estático que contenga valores enum como clave y la funcionalidad como valor. De esta forma, puede probar fácilmente si para cualquier valor enum tiene un comportamiento válido al recorrer todos los valores. Puede ser un poco exagerado en este ejemplo, pero esta es una técnica que uso a menudo para asignar identificadores de recursos a valores enum .

jMock (al menos a partir de la versión 2.5.1 que estoy usando) puede hacerlo de la caja. Tendrá que configurar su burla para usar ClassImposterizer.

 Mockery mockery = new Mockery(); mockery.setImposterizer(ClassImposterizer.INSTANCE); MyEnum unexpectedValue = mockery.mock(MyEnum.class); 

En primer lugar, Mockito puede crear datos simulados que pueden ser de un número entero, etc. No puede crear una enumeración correcta, ya que enum tiene un número específico de valor de nombre ordinal, etc. por lo que si tengo una enumeración

 public enum HttpMethod { GET, POST, PUT, DELETE, HEAD, PATCH; } 

así que tengo 5 ordinales en enum HttpMethod pero Mockito no lo sabe. Mockito crea datos falsos y es nulo todo el tiempo y terminarás pasando un valor nulo. Así que aquí está la solución propuesta de aleatorizar el ordinal y obtener una enumeración correcta que se puede pasar para otra prueba

 import static org.mockito.Mockito.mock; import java.util.Random; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Matchers; import org.mockito.internal.util.reflection.Whitebox; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import com.amazonaws.HttpMethod; //@Test(expected = {"LoadableBuilderTestGroup"}) //@RunWith(PowerMockRunner.class) public class testjava { // private static final Class HttpMethod.getClass() = null; private HttpMethod mockEnumerable; @Test public void setUpallpossible_value_of_enum () { for ( int i=0 ;i<10;i++){ String name; mockEnumerable= Matchers.any(HttpMethod.class); if(mockEnumerable!= null){ System.out.println(mockEnumerable.ordinal()); System.out.println(mockEnumerable.name()); System.out.println(mockEnumerable.name()+"mocking suceess"); } else { //Randomize all possible value of enum Random rand = new Random(); int ordinal = rand.nextInt(HttpMethod.values().length); // 0-9. mockEnumerable= mockEnumerable= HttpMethod.values()[ordinal]; System.out.println(mockEnumerable.ordinal()); System.out.println(mockEnumerable.name()); } } } @Test public void setUpallpossible_value_of_enumwithintany () { for ( int i=0 ;i<10;i++){ String name; mockEnumerable= Matchers.any(HttpMethod.class); if(mockEnumerable!= null){ System.out.println(mockEnumerable.ordinal()); System.out.println(mockEnumerable.name()); System.out.println(mockEnumerable.name()+"mocking suceess"); } else { int ordinal; //Randomize all possible value of enum Random rand = new Random(); int imatch = Matchers.anyInt(); if( imatch>HttpMethod.values().length) ordinal = 0 ; else ordinal = rand.nextInt(HttpMethod.values().length); // 0-9. mockEnumerable= mockEnumerable= HttpMethod.values()[ordinal]; System.out.println(mockEnumerable.ordinal()); System.out.println(mockEnumerable.name()); } } } } 

Salida:

 0 GET 0 GET 5 PATCH 5 PATCH 4 HEAD 5 PATCH 3 DELETE 0 GET 4 HEAD 2 PUT 

Yo pondría el caso predeterminado con uno de los casos enum:

  public static enum MyEnum {A, B} public int foo(MyEnum value) { if (value == null) throw new IllegalArgumentException("Do not know how to handle " + value); switch(value) { case(A): return calculateSomething(); case(B): default: return calculateSomethingElse(); } }