Mockito 2 - LyonJug - 2017-01-26

Mockito 2 - LyonJug - 2017-01-26

Mockito n’est pas le premier framework de mock, il fait partie d’une évolution du genre. Dans cette présentation nous allons voir quelles sont les évolutions apportées par la version 2 : support Java 8, amélioration des API, qualité du code de test, support Android, continuous delivery, capacité de configuration. Nous allons aussi soulever le capot pour étudier la création d’un mock. Et enfin nous regarderons vers le futur pour Mockito 3.

------

The slides are french but here's the english description :
Mockito is not the first mock framework out there, it is part of long ongoing effort. In this talk we will see what Mockito 2 brings with version 2 : Java 8 support, API improvements, mock code quality, Android support, continuous delivery, configurability. We will also look at how mocks are made. And we will look at what is the prelimiary plan for Mockito 3.

F31c7fbcbb0766d0632d96fd7e74b649?s=128

Brice Dutheil

January 26, 2017
Tweet

Transcript

  1. Brice Dutheil LyonJUG 2017-01-26

  2. Qui suis-je ? Back to basics Mockito 2 Nouvelles features

    Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum
  3. Qui suis-je ? Back to basics Mockito 2 Nouvelles features

    Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum
  4. Brice Dutheil Développeur freelance

  5. Qui suis-je ? Back to basics Mockito 2 Nouvelles features

    Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum
  6. Un simulacre Une mauvaise plaisanterie Un mock c’est

  7. Bien sûr J’ai tous leurs goodies Non Vous pouvez répéter

    la question ? Qui connait Mockito ?
  8. Avec Mockito 1.x on avait déjà

  9. Les mocks

  10. IMethods a_lot_of_methods = mock(IMethods.class); MethodsImpl i_m_a_partial_mock = spy(new MethodsImpl());

  11. Les stubs

  12. IMethods a_lot_of_methods = mock(IMethods.class); MethodsImpl i_m_a_partial_mock = spy(new MethodsImpl()); given(a_lot_of_methods.varargs("Am",

    "stram", "gram", "Pic", "et", "pic", "et", "colégram", "Bour", "et", "bour", "et", "ratatam", "Am", "stram", "gram")).willReturn(42); // actual tested code
  13. La vérification

  14. IMethods a_lot_of_methods = mock(IMethods.class); MethodsImpl i_m_a_partial_mock = spy(new MethodsImpl()); given(a_lot_of_methods.varargs("Am",

    "stram", "gram", "Pic", "et", "pic", "et", "colégram", "Bour", "et", "bour", "et", "ratatam", "Am", "stram", "gram")).willReturn(42); // actual tested code verify(a_lot_of_methods).varargs(any());
  15. Les annotations

  16. @Mock IMethods a_lot_of_methods; @Spy MethodsImpl i_m_a_partial_mock = new MethodsImpl(); given(a_lot_of_methods.varargs("Am",

    "stram", "gram", "Pic", "et", "pic", "et", "colégram", "Bour", "et", "bour", "et", "ratatam", "Am", "stram", "gram")).willReturn(42); // actual tested code verify(a_lot_of_methods).varargs(any());
  17. Une intégration JUnit 4

  18. @RunWith(MockitoJUnitRunner.Silent.class) public class WithARunnerTest { }

  19. @RunWith(MockitoJUnitRunner.Silent.class) public class WithARunnerTest { } public class WithARuleTest {

    @Rule public MockitoRule rulez = MockitoJUnit.rule(); }
  20. Les utilitaires complémentaires

  21. @Mock IMethods a_lot_of_methods; @Spy MethodsImpl i_m_a_partial_mock = new MethodsImpl(); given(a_lot_of_methods.varargs("Am",

    "stram", "gram", "Pic", "et", "pic", "et", "colégram", "Bour", "et", "bour", "et", "ratatam", "Am", "stram", "gram")).willReturn(42); // actual tested code verify(a_lot_of_methods).varargs(any()); ArgumentCaptor<Object> the_arg = ArgumentCaptor.forClass(Long.class); verify(i_m_a_partial_mock).oneArg(the_arg.capture());
  22. @Mock IMethods a_lot_of_methods; @Spy MethodsImpl i_m_a_partial_mock = new MethodsImpl(); given(a_lot_of_methods.varargs("Am",

    "stram", "gram", "Pic", "et", "pic", "et", "colégram", "Bour", "et", "bour", "et", "ratatam", "Am", "stram", "gram")) .will(AdditionalAnswers.returnsArgAt(9)); // actual tested code verify(a_lot_of_methods).varargs(any()); ArgumentCaptor<Object> The_arg = ArgumentCaptor.forClass(Long.class); verify(i_m_a_partial_mock).oneArg(The_arg.capture()); Pour les cas plus rares
  23. Qui suis-je ? Back to basics Mockito 2 Nouvelles features

    Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum
  24. Java 8

  25. Mockito retourne des valeurs par défaut pour certains types

  26. public Long returns_a_long() { } mock.returns_a_long() Retourne 0

  27. public Long returns_a_long() { } mock.returns_a_long() public List<Thing> returns_a_list() {

    } mock.returns_a_list() Retourne une collection vide
  28. Java 8 vient avec des nouveaux types

  29. public Optional<Thing> returns_optional() { } mock.returns_optional() public Stream<Thing> returns_a_stream() {

    } mock.returns_a_stream() Retourne Optional.empty() Retourne une stream vide
  30. Mockito 2 est compilé avec Java 6 donc sans Stream

    et Optional
  31. Stream et Optional ne sont donc pas disponible dans l’API

    publique de Mockito
  32. L’API des matchers re-pensée avec l’inférence de type

  33. String forBoolean(Boolean value); when(mock.forBoolean(isNull())).thenReturn("ok"); String forInteger(Integer value); when(mock.forInteger(notNull())).thenReturn("ok"); String forMap(Map<String,

    String> map); when(mock.forMap(anyMap())).thenReturn("ok"); String oneArg(CurrencyDescription value); when(mock.oneArg(any())).thenReturn("$ USD"); when(mock.oneArg(any(EuroDescription.class))).thenReturn("€ EUR"); Java 8
  34. Avec Java 6 ou Java 7, il est possible d’utiliser

    l’ancienne API
  35. String forBoolean(Boolean value); when(mock.forBoolean(isNull())).thenReturn("ok"); String forInteger(Integer value); when(mock.forInteger(notNull())).thenReturn("ok"); String forMap(Map<String,

    String> map); when(mock.forMap(anyMap())).thenReturn("ok"); String oneArg(CurrencyDescription value); when(mock.oneArg(any())).thenReturn("$ USD"); when(mock.oneArg(any(EuroDescription.class))).thenReturn("€ EUR"); Java 8
  36. String forBoolean(Boolean value); when(mock.forBoolean(isNull(Boolean.class))).thenReturn("ok"); String forInteger(Integer value); when(mock.forInteger(notNull(Integer.class))).thenReturn("ok"); String forMap(Map<String,

    String> map); when(mock.forMap(anyMapOf(String.class, String.class))).thenReturn("2"); String oneArg(CurrencyDescription value); when(mock.oneArg((CurrencyDescription) any())).thenReturn("matched"); when(mock.oneArg(any(EuroDescription.class))).thenReturn("€ EUR"); Java 7
  37. L’ancienne API est dépréciée car elle n’a plus d’intérêt avec

    l’inférence de type
  38. Pour faciliter la transition : Utiliser la nouvelle API avec

    des cast en Java 6/7
  39. String forBoolean(Boolean value); when(mock.forBoolean((Boolean) isNull())).thenReturn("ok"); String forInteger(Integer value); when(mock.forInteger((Integer) notNull())).thenReturn("ok");

    String forMap(Map<String, String> map); when(mock.forMap((Map<String, String>) anyMap())).thenReturn("ok"); String oneArg(CurrencyDescription value); when(mock.oneArg((CurrencyDescription) any())).thenReturn("$ USD"); when(mock.oneArg(any(EuroDescription.class))).thenReturn("€ EUR");
  40. Votre IDE proposera de retirer les instructions de cast avec

    Java 8
  41. String forBoolean(Boolean value); when(mock.forBoolean((Boolean) isNull())).thenReturn("ok");

  42. String forBoolean(Boolean value); when(mock.forBoolean((Boolean) isNull())).thenReturn("ok");

  43. L’API publique a été améliorée pour profiter les lambdas

  44. given(mock.returns_a_long()).willAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable

    { // custom code ... return something; } }); Mockito 1.x et Mockito 2.x
  45. given(mock.returns_a_long()).willAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable

    { // custom code ... return something; } }); given(mock.returns_a_long()).willAnswer(invocation -> /* custom code ... */); Mockito 1.x
  46. given(mock.returns_a_long()).willAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable

    { // custom code ... return something; } }); given(mock.returns_a_long()).willAnswer(invocation -> /* custom code ... */); Représente une invocation sur le mock
  47. import static org.mockito.AdditionalAnswers.answer; given(mock.returns_a_long("a", "b", "c")).will(answer((arg0, arg1, arg2) -> 0));

    Mockito 2.x
  48. import static org.mockito.AdditionalAnswers.answer; given(mock.returns_a_long("a", "b", "c")).will(answer((arg0, arg1, arg2) -> 0));

    Arguments de la méthode
  49. import static org.mockito.AdditionalAnswers.answer; given(mock.returns_a_long("a", "b", "c")).will(answer((arg0, arg1, arg2) -> 0));

    Answer qui extrait les arguments de l’invocation
  50. import static org.mockito.AdditionalAnswers.answer; given(mock.returns_a_long("a", "b", "c")).will(answer((arg0, arg1, arg2) -> 0));

  51. Avec Mockito 3 ce travail ira plus loin

  52. Java 8 permet d’écrire du code concret dans les méthodes

    d’interface connues sous le nom default methods
  53. Le comportement par défaut de Mockito 2 est de stubber

    ces méthodes
  54. @Rule public MockitoRule rulez = MockitoJUnit.rule(); @Mock private Consumer<String> consumer1;

    @Mock private Consumer<String> consumer2;
  55. @Rule public MockitoRule rulez = MockitoJUnit.rule(); @Mock private Consumer<String> consumer1;

    @Mock private Consumer<String> consumer2; @Test public void working_with_default_methods() { consumer1.andThen(consumer2) .accept("hi"); }
  56. @Rule public MockitoRule rulez = MockitoJUnit.rule(); @Mock private Consumer<String> consumer1;

    @Mock private Consumer<String> consumer2; @Test public void working_with_default_methods() { consumer1.andThen(consumer2) .accept("hi"); verify(consumer1).accept("hi"); verify(consumer2).accept("hi"); }
  57. @Rule public MockitoRule rulez = MockitoJUnit.rule(); @Mock private Consumer<String> consumer1;

    @Mock private Consumer<String> consumer2; @Test public void working_with_default_methods() { consumer1.andThen(consumer2) .accept("hi"); verify(consumer1).accept("hi"); verify(consumer2).accept("hi"); } NullPointerException
  58. @Rule public MockitoRule rulez = MockitoJUnit.rule(); @Mock private Consumer<String> consumer1;

    @Mock private Consumer<String> consumer2; @Test public void working_with_default_methods() { consumer1.andThen(consumer2) .accept("hi"); verify(consumer1).accept("hi"); verify(consumer2).accept("hi"); } Retourne null
  59. @Rule public MockitoRule rulez = MockitoJUnit.rule(); @Mock private Consumer<String> consumer1;

    @Mock private Consumer<String> consumer2; @Test public void working_with_default_methods() { consumer1.andThen(consumer2) .accept("hi"); verify(consumer1).accept("hi"); verify(consumer2).accept("hi"); } default method
  60. @Rule public MockitoRule rulez = MockitoJUnit.rule(); @Mock private Consumer<String> consumer1;

    @Mock private Consumer<String> consumer2; @Test public void working_with_default_methods() { consumer1.andThen(consumer2) .accept("hi"); verify(consumer1).accept("hi"); verify(consumer2).accept("hi"); } default method default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; }
  61. À ce jour les mocks d’interface n’invoquent pas le comportement

    concret des default methods
  62. Un spy est un mock partiel. Par défaut invoque les

    méthodes concrètes mais permet de modifier le comportement
  63. Créer un spy à partir de classes abstraites ou d’interfaces

    est maintenant possible
  64. Consumer<String> consumer1 = spy(Consumer.class); Consumer<String> consumer2 = spy(Consumer.class); consumer1.andThen(consumer2) .accept("hi");

    verify(consumer1).accept("hi"); verify(consumer2).accept("hi");
  65. Consumer<String> consumer1 = spy(Consumer.class); Consumer<String> consumer2 = spy(Consumer.class); consumer1.andThen(consumer2) .accept("hi");

    verify(consumer1).accept("hi"); verify(consumer2).accept("hi"); Code concret de andThen exécuté
  66. Qualité

  67. La pratique du TDD permet de maintenir la qualité du

    code de production
  68. Qu’en est-il du code de test ?

  69. public class Tested { private Dep1 dep1; private Dep2 dep2;

    private Dep3 dep3; public void behavior() { dep1.sub_behaviour(); dep2.sub_behaviour(); dep3.sub_behaviour(); } }
  70. @Rule public MockitoRule rulez = MockitoJUnit.rule(); @Mock private Dep1 dep1;

    @Mock private Dep2 dep2; @Mock private Dep3 dep3; @InjectMocks Tested tested; @Test public void should_behave_in_that_case() { given(dep1.sub_behaviour()).willReturn("A"); given(dep2.sub_behaviour()).willReturn("A"); given(dep3.sub_behaviour()).willReturn("A"); tested.behavior(); //... }
  71. public class Tested { private Dep1 dep1; private Dep2 dep2;

    private Dep3 dep3; public void behavior() { dep1.sub_behaviour(); dep2.sub_behaviour(); dep3.sub_behaviour(); } } Feature supprimée
  72. public class Tested { private Dep1 dep1; private Dep2 dep2;

    private Dep3 dep3; public void behavior() { dep3.sub_behaviour(); } } Feature supprimée
  73. public class Tested { private Dep3 dep3; public void behavior()

    { dep3.sub_behaviour(); } }
  74. [MockitoHint] QualityTest.should_behave_in_that_case (see javadoc for MockitoHint): [MockitoHint] 1. Unused ->

    at QualityTest.should_behave_in_that_case(QualityTest.java:22) [MockitoHint] 2. Unused -> at QualityTest.should_behave_in_that_case(QualityTest.java:23)
  75. @Rule public MockitoRule rulez = MockitoJUnit.rule(); @Mock private Dep1 dep1;

    @Mock private Dep2 dep2; @Mock private Dep3 dep3; @InjectMocks Tested tested; @Test public void should_behave_in_that_case() { given(dep1.sub_behaviour()).willReturn("A"); given(dep2.sub_behaviour()).willReturn("A"); given(dep3.sub_behaviour()).willReturn("A"); tested.behavior(); //... }
  76. @Rule public MockitoRule rulez = MockitoJUnit.rule() .silent(); @Mock private Dep1

    dep1; @Mock private Dep2 dep2; @Mock private Dep3 dep3; @InjectMocks Tested tested; @Test public void should_behave_in_that_case() { given(dep1.sub_behaviour()).willReturn("A"); given(dep2.sub_behaviour()).willReturn("A"); given(dep3.sub_behaviour()).willReturn("A"); tested.behavior(); //... } Éteint les warnings dans la console
  77. @Rule public MockitoRule rulez = MockitoJUnit.rule() .strictness(STRICT_STUBS); @Mock private Dep1

    dep1; @Mock private Dep2 dep2; @Mock private Dep3 dep3; @InjectMocks Tested tested; @Test public void should_behave_in_that_case() { given(dep1.sub_behaviour()).willReturn("A"); given(dep2.sub_behaviour()).willReturn("A"); given(dep3.sub_behaviour()).willReturn("A"); tested.behavior(); //... } Fait échouer le test
  78. @Rule public MockitoRule rulez = MockitoJUnit.rule() .strictness(STRICT_STUBS); @Mock private Dep1

    dep1; @Mock private Dep2 dep2; @Mock private Dep3 dep3; @InjectMocks Tested tested; @Test public void should_behave_in_that_case() { given(dep1.sub_behaviour()).willReturn("A"); given(dep2.sub_behaviour()).willReturn("A"); given(dep3.sub_behaviour()).willReturn("A"); tested.behavior(); //... } Possiblement le défaut dans Mockito 3
  79. org.mockito.exceptions.misusing.UnnecessaryStubbingException: Unnecessary stubbings detected. Clean & maintainable test code requires

    zero unnecessary code. Following stubbings are unnecessary (click to navigate to relevant line of code): 1. -> at QualityTest.should_behave_in_that_case(QualityTest.java:23) 2. -> at QualityTest.should_behave_in_that_case(QualityTest.java:24) Please remove unnecessary stubbings or use 'silent' option. More info: javadoc for UnnecessaryStubbingException class.
  80. Le runner MockitoJunitRunner est stricte par défaut. Changeable avec MockitoJUnitRunner.Silent

  81. Plugins

  82. L’extensibilité et la facilité de configuration existent depuis longtemps.

  83. Le tout premier mécanisme consiste à créer dans ses sources

    une classe org.mockito.configuration.MockitoConfiguration
  84. Lorsque Mockito identifie cette classe dans le classpath, il tient

    compte des nouveaux réglages
  85. package org.mockito.configuration;

  86. package org.mockito.configuration; public class MockitoConfiguration { }

  87. package org.mockito.configuration; public class MockitoConfiguration implements IMockitoConfiguration { }

  88. package org.mockito.configuration; public class MockitoConfiguration implements IMockitoConfiguration { @Override public

    Answer<Object> getDefaultAnswer() { return ...; } @Override public AnnotationEngine getAnnotationEngine() { return ...; } @Override public boolean cleansStackTrace() { return ...; } @Override public boolean enableClassCache() { return ...; } }
  89. package org.mockito.configuration; public class MockitoConfiguration extends DefaultMockitoConfiguration implements IMockitoConfiguration {

    @Override public Answer<Object> getDefaultAnswer() { return ...; } @Override public AnnotationEngine getAnnotationEngine() { return ...; } @Override public boolean cleansStackTrace() { return ...; } @Override public boolean enableClassCache() { return ...; } }
  90. package org.mockito.configuration; public class MockitoConfiguration extends DefaultMockitoConfiguration implements IMockitoConfiguration {

    @Override public boolean cleansStackTrace() { return ...; } @Override public boolean enableClassCache() { return ...; } }
  91. Demande de Google (Jessie Wilson), pour permettre à Mockito de

    fonctionner sur la VM Dalvik
  92. Un nouveau mécanisme de plugins à été introduit en version

    1.9.5
  93. Création de l’interface MockMaker

  94. Charge la classe du plugin déclaré dans le fichier mockito-extensions/org.mockito.plugins.MockMaker

  95. Fonctionnement similaire au ServiceLoader

  96. Pourquoi ne pas utiliser ServiceLoader ?

  97. Pourquoi ne pas utiliser ServiceLoader ? Machines will replace humans

  98. Pourquoi ne pas utiliser ServiceLoader ? Ajouté dans le niveau

    d’API 9 (~2011)
  99. Pourquoi ne pas utiliser ServiceLoader ? Ajouté dans le niveau

    d’API 9 (~2011) Utilise dans le dossier META-INF hors il est supprimé à la création de l’APK
  100. Mockito 2 étends le nombre de plugins Interfaces dans org.mockito.plugins

  101. MockMaker, génère un mock

  102. MockMaker, génère un mock InstantiatorProvider, instancie un mock

  103. MockMaker, génère un mock InstantiatorProvider, instancie un mock AnnotationEngine, gère

    les annotations
  104. MockMaker, génère un mock InstantiatorProvider, instancie un mock AnnotationEngine, gère

    les annotations StackTraceCleanerProvider, néttoie les stack traces
  105. MockMaker, génère un mock InstantiatorProvider, instancie un mock AnnotationEngine, gère

    les annotations StackTraceCleanerProvider, néttoie les stack traces PluginSwitch, gère l’activation d’un plugin externe
  106. Exemple : Ajouter la gestion de l’annotation @Dummy

  107. /** * Serves as a marker for dummy mocks, when

    processed creates * a mock using {@code Mcokito.mock(FieldType.class)} */ public @interface Dummy { }
  108. /** * Serves as a marker for dummy mocks, when

    processed creates * a mock using {@code Mcokito.mock(FieldType.class)} */ public @interface Dummy { } @Dummy Dependency just_for_compilation;
  109. public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>

    clazz, Object testInstance) { } }
  110. public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>

    clazz, Object testInstance) { testInstance.getClass().getDeclaredFields() } }
  111. public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>

    clazz, Object testInstance) { Arrays.stream(testInstance.getClass().getDeclaredFields()) } }
  112. public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>

    clazz, Object testInstance) { Arrays.stream(testInstance.getClass().getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Dummy.class)) } }
  113. public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>

    clazz, Object testInstance) { Arrays.stream(testInstance.getClass().getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Dummy.class)) .forEach(field -> { }); } }
  114. public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>

    clazz, Object testInstance) { Arrays.stream(testInstance.getClass().getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Dummy.class)) .forEach(field -> { field.set(testInstance, Mockito.mock(field.getType())); }); } }
  115. public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>

    clazz, Object testInstance) { Arrays.stream(testInstance.getClass().getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Dummy.class)) .forEach(field -> { field.setAccessible(true); field.set(testInstance, Mockito.mock(field.getType())); }); } }
  116. public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>

    clazz, Object testInstance) { Arrays.stream(testInstance.getClass().getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Dummy.class)) .forEach(field -> { try { field.setAccessible(true); field.set(testInstance, Mockito.mock(field.getType())); } catch (Exception e) { } }); } }
  117. public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>

    clazz, Object testInstance) { Arrays.stream(testInstance.getClass().getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Dummy.class)) .forEach(field -> { try { field.setAccessible(true); field.set(testInstance, Mockito.mock(field.getType())); } catch (Exception e) { throw new IllegalStateException(format("Could not initialize @Dummy field '%s'", field.getName()), e); } }); } }
  118. mockito-extensions/org.mockito.plugins.AnnotationEngine

  119. mockito-extensions/org.mockito.plugins.AnnotationEngine my.package.DummyAnnotationEngine

  120. Ce processeur d’annotation remplace le processeur par défaut

  121. Les annotations @Mock, @Spy, etc. sont ignorées

  122. Astuce temporaire

  123. public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>

    clazz, Object testInstance) { Arrays.stream(testInstance.getClass().getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Dummy.class)) .forEach(field -> { try { field.setAccessible(true); field.set(testInstance, Mockito.mock(field.getType())); } catch (Exception e) { throw new IllegalStateException(format("Could not initialize @Dummy field '%s'", field.getName()), e); } }); } }
  124. public class DummyAnnotationEngine implements AnnotationEngine { AnnotationEngine internalEngine = new

    InjectingAnnotationEngine(); @Override public void process(Class<?> clazz, Object testInstance) { Arrays.stream(testInstance.getClass().getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Dummy.class)) .forEach(field -> { try { field.setAccessible(true); field.set(testInstance, Mockito.mock(field.getType())); } catch (Exception e) { throw new IllegalStateException(format("Could not initialize @Dummy field '%s'", field.getName()), e); } }); } }
  125. public class DummyAnnotationEngine implements AnnotationEngine { AnnotationEngine internalEngine = new

    InjectingAnnotationEngine(); @Override public void process(Class<?> clazz, Object testInstance) { Arrays.stream(testInstance.getClass().getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Dummy.class)) .forEach(field -> { try { field.setAccessible(true); field.set(testInstance, Mockito.mock(field.getType())); } catch (Exception e) { throw new IllegalStateException(format("Could not initialize @Dummy field '%s'", field.getName()), e); } }); internalEngine.process(clazz, testInstance); } } Invoque le processeur interne
  126. public class DummyAnnotationEngine implements AnnotationEngine { AnnotationEngine internalEngine = new

    InjectingAnnotationEngine(); @Override public void process(Class<?> clazz, Object testInstance) { internalEngine.process(clazz, testInstance); } } API interne
  127. Une évolution de l’API publique permettra d’obtenir le plugin par

    défaut de Mockito
  128. Android

  129. Un robot humanoïde Un OS pour mobile Qui connait Android

    ?
  130. Android Studio permet d’écrire deux types de test

  131. Les tests qui vont tourner sur une VM java Usuellement

    hotspot
  132. Les tests qui vont tourner sur un émulateur Ou un

    device
  133. None
  134. Ces deux packages de tests ont chacun leur scopes de

    dépendances
  135. testCompile pour les tests qui vont tourner sur la VM

    java
  136. androidTestCompile pour les tests qui vont tourner sur l’émulateur /

    le device
  137. dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude

    group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.1.0' // ... testCompile 'junit:junit:4.12' // ... }
  138. dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude

    group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.1.0' // ... testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:2.+' // ... }
  139. dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude

    group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.1.0' // ... testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:2.+' androidTestCompile 'org.mockito:mockito-core:2.+' androidTestCompile 'com.linkedin.dexmaker:dexmaker-mockito:2.2.0' // ... }
  140. dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude

    group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.1.0' // ... testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:2.+' androidTestCompile 'org.mockito:mockito-core:2.+' androidTestCompile 'com.linkedin.dexmaker:dexmaker-mockito:2.2.0' // ... } dexmaker a été repris par linkedin Et mis à jour pour fonctionner avec Mockito 2
  141. Depuis la version 2.6.1 nous avons une alternative à dexmaker

    : mockito-android
  142. Celle-ci est basée sur bytebuddy-android.

  143. dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude

    group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.1.0' // ... testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:2.+' androidTestCompile 'org.mockito:mockito-core:2.+' androidTestCompile 'com.linkedin.dexmaker:dexmaker-mockito:2.2.0' // ... }
  144. dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude

    group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.1.0' // ... testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:2.+' androidTestCompile 'org.mockito:mockito-core:2.+' androidTestCompile 'org.mockito:mockito-android:2.6.+' // ... }
  145. Mieux que dexmaker mais il y a encore des améliorations

  146. Classes finales

  147. Mockito 1 limité aux classes non finale

  148. Mockito 2 introduit un mockmaker expérimental pour les classes finales

  149. Mockito 2 introduit un mockmaker expérimental pour les classes finales

  150. mockito-extensions/org.mockito.plugins.MockMaker mock-maker-inline

  151. public final class FinalClass { public String foo() { return

    "foo"; } }
  152. public final class FinalClass { public String foo() { return

    "foo"; } } FinalClass finalClass = mock(FinalClass.class); given(finalClass.foo()).willReturn("bar");
  153. public final class FinalClass { public String foo() { return

    "foo"; } } FinalClass finalClass = mock(FinalClass.class); given(finalClass.foo()).willReturn("bar"); assertThat(proxy.foo()).isEqualTo("bar");
  154. public final class FinalClass { public String foo() { return

    "foo"; } } FinalClass finalClass = mock(FinalClass.class); given(finalClass.foo()).willReturn("bar"); assertThat(proxy.foo()).isEqualTo("bar"); My Life List … [✔ ] Mocking final
  155. Même avec les classes du JDK ! À quelques exceptions

    près Class, String, …
  156. Attention !

  157. Toujours questionner s’il est sage de mocker un type qui

    n’est pas sous notre contrôle
  158. Vous ne voulez pas mocker Pattern, classe finale, son comportement

    change entre chaque JDK
  159. Vous ne voulez pas mocker HashMap, classe non finale, son

    comportement change entre chaque JDK
  160. Sur votre CI vos tests peuvent être verts mais le

    code exécuté en production peut être erroné
  161. Qui suis-je ? Back to basics Mockito 2 Nouvelles features

    Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum
  162. Un doudou pour les bébés Le remplaçant de CGLIB Qu’est-ce

    que byte-buddy ?
  163. CGLIB de facto standard nous faisait défaut sur de nombreux

    points
  164. Bridge method, annotations, generics, permgen leak, aucun mainteneurs, CVS, etc.

  165. Après des mois à coder et étudier les alternatives, http://stackoverflow.com/questions/2261947/are-there-alternatives-to-cglib/9823788#9823788

  166. Après des mois à coder et étudier les alternatives, Rafael

    nous a proposé sa librairie : byte-buddy http://stackoverflow.com/questions/2261947/are-there-alternatives-to-cglib/9823788#9823788
  167. Le mock maker de mockito 1 est basé sur CGLIB

  168. ByteBuddy a une API basé sur des builders

  169. … relativement avancée, elle demande un certain niveau de connaissances

    sur la JVM
  170. … relativement avancée, elle demande un certain niveau de connaissance

    sur la JVM Mais pas autant que ASM
  171. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); if (features.serializableMode == SerializableMode.ACROSS_CLASSLOADERS) { builder = builder.implement(CrossClassLoaderSerializableMock.class) .intercept(to(MockMethodInterceptor.ForWriteReplace.class)); } if (readReplace != null) { builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE) .withParameters(ObjectInputStream.class) .throwing(ClassNotFoundException.class, IOException.class) .intercept(readReplace); } return builder.make() .load(new MultipleParentClassLoader.Builder() .append(features.mockedType) .append(features.interfaces) .append(currentThread().getContextClassLoader()) .append(MockAccess.class, DispatcherDefaultingToRealMethod.class) .append(MockMethodInterceptor.class, MockMethodInterceptor.ForHashCode.class,
  172. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); if (features.serializableMode == SerializableMode.ACROSS_CLASSLOADERS) { builder = builder.implement(CrossClassLoaderSerializableMock.class) .intercept(to(MockMethodInterceptor.ForWriteReplace.class)); } if (readReplace != null) { builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE) .withParameters(ObjectInputStream.class) .throwing(ClassNotFoundException.class, IOException.class) .intercept(readReplace); } return builder.make() .load(new MultipleParentClassLoader.Builder() .append(features.mockedType) .append(features.interfaces) .append(currentThread().getContextClassLoader()) .append(MockAccess.class, DispatcherDefaultingToRealMethod.class) .append(MockMethodInterceptor.class, MockMethodInterceptor.ForHashCode.class, Configuration de la classe
  173. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); if (features.serializableMode == SerializableMode.ACROSS_CLASSLOADERS) { builder = builder.implement(CrossClassLoaderSerializableMock.class) .intercept(to(MockMethodInterceptor.ForWriteReplace.class)); } if (readReplace != null) { builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE) .withParameters(ObjectInputStream.class) .throwing(ClassNotFoundException.class, IOException.class) .intercept(readReplace); } return builder.make() .load(new MultipleParentClassLoader.Builder() .append(features.mockedType) .append(features.interfaces) .append(currentThread().getContextClassLoader()) .append(MockAccess.class, DispatcherDefaultingToRealMethod.class) .append(MockMethodInterceptor.class, MockMethodInterceptor.ForHashCode.class, Configuration de la classe Configuration du classloader pour le chargement de la classe du mock
  174. Comment Mockito créé ses mocks ?

  175. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, Visibility.PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); Type de modification : Sous classe
  176. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, Visibility.PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); Défini le nom qualifié Packages signés, etc.
  177. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, Visibility.PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); Le nom de la classe contiendra : MockitoMock
  178. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, Visibility.PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); On ne mocke pas les méthodes générées par groovy
  179. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, Visibility.PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); Copie les annotations du type mocké
  180. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, Visibility.PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); Extra interfaces
  181. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); Quelle méthode à intercepter (toutes par défaut)
  182. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); Type du callback Dispatcher : Classe mockito annotée
  183. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); Copie les annotations de la méthode
  184. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); hashCode / equals ne sont pas mockable
  185. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); La réponse à la grande question
  186. DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType) .name(nameFor(features.mockedType)) .ignoreAlso(isGroovyMethod()) .annotateType(features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(features.interfaces)) .method(matcher)

    .intercept(to(DispatcherDefaultingToRealMethod.class)) .transform(withModifiers(SynchronizationState.PLAIN, Visibility.PUBLIC)) .attribute(INCLUDING_RECEIVER) .method(isHashCode()) .intercept(to(MockMethodInterceptor.ForHashCode.class)) .method(isEquals()) .intercept(to(MockMethodInterceptor.ForEquals.class)) .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) .implement(MockAccess.class) .intercept(FieldAccessor.ofBeanProperty()); Champs et accesseurs de l’intercepteur mockito
  187. Comment Mockito intercepte les appels ?

  188. public static class DispatcherDefaultingToRealMethod { @SuppressWarnings("unused") @RuntimeType @BindingPriority(BindingPriority.DEFAULT * 2)

    public static Object interceptSuperCallable(@This Object mock, @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor, @Origin Method invokedMethod, @AllArguments Object[] arguments, @SuperCall(serializableProxy= true) Callable<?> superCall) throws Throwable { if (interceptor == null) { return superCall.call(); } return interceptor.doIntercept( mock, invokedMethod, arguments, new InterceptedInvocation.SuperMethod.FromCallable(superCall) ); } @SuppressWarnings("unused") @RuntimeType public static Object interceptAbstract(@This Object mock, @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor, @StubValue Object stubValue, @Origin Method invokedMethod, @AllArguments Object[] arguments) throws Throwable { if (interceptor == null) { return stubValue;
  189. public static class DispatcherDefaultingToRealMethod { @SuppressWarnings("unused") @RuntimeType @BindingPriority(BindingPriority.DEFAULT * 2)

    public static Object interceptSuperCallable(@This Object mock, @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor, @Origin Method invokedMethod, @AllArguments Object[] arguments, @SuperCall(serializableProxy= true) Callable<?> superCall) throws Throwable { if (interceptor == null) { return superCall.call(); } return interceptor.doIntercept( mock, invokedMethod, arguments, new InterceptedInvocation.SuperMethod.FromCallable(superCall) ); } @SuppressWarnings("unused") @RuntimeType public static Object interceptAbstract(@This Object mock, @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor, @StubValue Object stubValue, @Origin Method invokedMethod, @AllArguments Object[] arguments) throws Throwable { if (interceptor == null) { Appelé lorsqu’une invocation à lieu
  190. public static class DispatcherDefaultingToRealMethod { @SuppressWarnings("unused") @RuntimeType @BindingPriority(BindingPriority.DEFAULT * 2)

    public static Object interceptSuperCallable(@This Object mock, @FieldValue("mockitoInterceptor") MockMethodInterceptor intercep @Origin Method invokedMethod, @AllArguments Object[] arguments, @SuperCall(serializableProxy= true) Callable<?> superCall) thro Throwable { if (interceptor == null) { return superCall.call(); } return interceptor.doIntercept( mock, invokedMethod, arguments, new InterceptedInvocation.SuperMethod.FromCallable(superCall) ); } @SuppressWarnings("unused") @RuntimeType public static Object interceptAbstract(@This Object mock, @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor, @StubValue Object stubValue, Indique à byte-buddy les éléments qui nous intéressent
  191. public static class DispatcherDefaultingToRealMethod { @SuppressWarnings("unused") @RuntimeType @BindingPriority(BindingPriority.DEFAULT * 2)

    public static Object interceptSuperCallable(@This Object mock, @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor, @Origin Method invokedMethod, @AllArguments Object[] arguments, @SuperCall(serializableProxy= true) Callable<?> superCall) throws Throwable { if (interceptor == null) { return superCall.call(); } return interceptor.doIntercept( mock, invokedMethod, arguments, new InterceptedInvocation.SuperMethod.FromCallable(superCall) ); } @SuppressWarnings("unused") @RuntimeType public static Object interceptAbstract(@This Object mock, @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor, @StubValue Object stubValue, @Origin Method invokedMethod, @AllArguments Object[] arguments) throws Throwable { Garde, appelle la méthode concrète de l’objet
  192. public static class DispatcherDefaultingToRealMethod { @SuppressWarnings("unused") @RuntimeType @BindingPriority(BindingPriority.DEFAULT * 2)

    public static Object interceptSuperCallable(@This Object mock, @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor, @Origin Method invokedMethod, @AllArguments Object[] arguments, @SuperCall(serializableProxy= true) Callable<?> superCall) throws Throwable { if (interceptor == null) { return superCall.call(); } return interceptor.doIntercept( mock, invokedMethod, arguments, new InterceptedInvocation.SuperMethod.FromCallable(superCall) ); } @SuppressWarnings("unused") @RuntimeType public static Object interceptAbstract(@This Object mock, @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor, @StubValue Object stubValue, Invoque la logique interne de Mockito
  193. public static class DispatcherDefaultingToRealMethod { // ... public static Object

    interceptSuperCallable(...) Callable<?> superCall) throws Throwable { ... } @SuppressWarnings("unused") @RuntimeType public static Object interceptAbstract(@This Object mock, @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor, @StubValue Object stubValue, @Origin Method invokedMethod, @AllArguments Object[] arguments) throws Throwable { if (interceptor == null) { return stubValue; } return interceptor.doIntercept( mock, invokedMethod, arguments, InterceptedInvocation.SuperMethod.IsIllegal.INSTANCE ); } } Callback utilisé pour les méthodes abstraites
  194. public static class DispatcherDefaultingToRealMethod { // ... public static Object

    interceptSuperCallable(...) Callable<?> superCall) throws Throwable { ... } @SuppressWarnings("unused") @RuntimeType public static Object interceptAbstract(@This Object mock, @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor, @StubValue Object stubValue, @Origin Method invokedMethod, @AllArguments Object[] arguments) throws Throwable { if (interceptor == null) { return stubValue; } return interceptor.doIntercept( mock, invokedMethod, arguments, InterceptedInvocation.SuperMethod.IsIllegal.INSTANCE ); } } Invoque la logique interne de Mockito
  195. Au final la stratégie de création est de créer une

    sous classe
  196. Ensuite la classe du mock sera instanciée par Objenesis

  197. C mock = mock(C.class);

  198. Type à mocker C mock = mock(C.class);

  199. Type à mocker Classe du mock extends C mock =

    mock(C.class);
  200. Le modifier final n’autorise pas d’étendre la hiérarchie

  201. Comment autoriser les mocks des classes finales ?

  202. Powermock ?

  203. @RunWith(PowerMockRunner.class) @PrepareForTest({C.class})

  204. @RunWith(PowerMockRunner.class) @PrepareForTest({C.class}) Classe finale

  205. @RunWith(PowerMockRunner.class) @PrepareForTest({C.class}) Classloader PowerMock Classe finale Nouveau classloader

  206. @RunWith(PowerMockRunner.class) @PrepareForTest({C.class}) Classloader PowerMock 010010111010… Reload la classe Classe finale

    Nouveau classloader
  207. @RunWith(PowerMockRunner.class) @PrepareForTest({C.class}) Classloader PowerMock 010010111010… Reload la classe Classe finale

    Supprime final Nouveau classloader
  208. @RunWith(PowerMockRunner.class) @PrepareForTest({C.class}) Classloader PowerMock 010010111010… Reload la classe Classe finale

    Supprime final Classe C définalisé Classe du mock extends Nouveau classloader
  209. Powermock recharge les classes du test dans un classloader qui

    retire final.
  210. Introduction du mock de classes finales avec Mockito 2

  211. Introduction du mock de classes finales avec Mockito 2

  212. Pas de classloader, on utilise un agent !

  213. L’agent est auto installé par byte-buddy Certaines VM non supportées

  214. C mock = mock(C.class); Classe finale Mockito + mock-maker-inline

  215. JVM C mock = mock(C.class); Classe finale

  216. JVM Installe l’agent C mock = mock(C.class); Classe finale Agent

    mockito
  217. static { try { try { instrumentation = ByteBuddyAgent.install(); if

    (!instrumentation.isRetransformClassesSupported()) { throw new IllegalStateException(...); } JarFile boot = createMockitoMethodDispatcherBootstrapJar(); instrumentation.appendToBootstrapClassLoaderSearch(boot); checkBootstrapJarInjection(); } catch (IOException ioe) { throw new IllegalStateException(..., ioe); } } catch (Throwable throwable) { instrumentation = null; initializationError = throwable; } INSTRUMENTATION = instrumentation; INITIALIZATION_ERROR = initializationError;
  218. static { try { try { instrumentation = ByteBuddyAgent.install(); if

    (!instrumentation.isRetransformClassesSupported()) { throw new IllegalStateException(...); } JarFile boot = createMockitoMethodDispatcherBootstrapJar(); instrumentation.appendToBootstrapClassLoaderSearch(boot); checkBootstrapJarInjection(); } catch (IOException ioe) { throw new IllegalStateException(..., ioe); } } catch (Throwable throwable) { instrumentation = null; initializationError = throwable; } INSTRUMENTATION = instrumentation; INITIALIZATION_ERROR = initializationError; Installation de l’agent
  219. static { try { try { instrumentation = ByteBuddyAgent.install(); if

    (!instrumentation.isRetransformClassesSupported()) { throw new IllegalStateException(...); } JarFile boot = createMockitoMethodDispatcherBootstrapJar(); instrumentation.appendToBootstrapClassLoaderSearch(boot); checkBootstrapJarInjection(); } catch (IOException ioe) { throw new IllegalStateException(..., ioe); } } catch (Throwable throwable) { instrumentation = null; initializationError = throwable; } INSTRUMENTATION = instrumentation; INITIALIZATION_ERROR = initializationError; Injecte le dispatcher mockito sur le classpath de bootstrap de la VM
  220. JVM C mock = mock(C.class); Classe finale Agent mockito

  221. JVM C mock = mock(C.class); Classe finale Agent mockito Transforme

    / instrumente la classe Indique à l’agent de modifier C
  222. JVM C mock = mock(C.class); Classe finale Agent mockito Ajoute

    l’intercepteur mockito dans la classe C
  223. @Override public <T> Class<? extends T> mockClass(MockFeatures<T> features) { boolean

    subclassingRequired = !features.interfaces.isEmpty() || features.serializableMode != SerializableMode.NONE || Modifier.isAbstract(features.mockedType.getModifiers()); checkSupportedCombination(subclassingRequired, features); synchronized (this) { triggerRetransformation(features); } return subclassingRequired ? subclassEngine.mockClass(features) : features.mockedType; }
  224. @Override public <T> Class<? extends T> mockClass(MockFeatures<T> features) { boolean

    subclassingRequired = !features.interfaces.isEmpty() || features.serializableMode != SerializableMode.NONE || Modifier.isAbstract(features.mockedType.getModifiers()); checkSupportedCombination(subclassingRequired, features); synchronized (this) { triggerRetransformation(features); } return subclassingRequired ? subclassEngine.mockClass(features) : features.mockedType; } Certaines fonctionnalités de mockito ne fonctionnent pas dans ce mode.
  225. @Override public <T> Class<? extends T> mockClass(MockFeatures<T> features) { boolean

    subclassingRequired = !features.interfaces.isEmpty() || features.serializableMode != SerializableMode.NONE || Modifier.isAbstract(features.mockedType.getModifiers()); checkSupportedCombination(subclassingRequired, features); synchronized (this) { triggerRetransformation(features); } return subclassingRequired ? subclassEngine.mockClass(features) : features.mockedType; } Instrumente la classe
  226. @Override public <T> Class<? extends T> mockClass(MockFeatures<T> features) { boolean

    subclassingRequired = !features.interfaces.isEmpty() || features.serializableMode != SerializableMode.NONE || Modifier.isAbstract(features.mockedType.getModifiers()); checkSupportedCombination(subclassingRequired, features); synchronized (this) { triggerRetransformation(features); } return subclassingRequired ? subclassEngine.mockClass(features) : features.mockedType; } Invoque instrumentation.retransformClasses(types);
  227. @Override public <T> Class<? extends T> mockClass(MockFeatures<T> features) { boolean

    subclassingRequired = !features.interfaces.isEmpty() || features.serializableMode != SerializableMode.NONE || Modifier.isAbstract(features.mockedType.getModifiers()); checkSupportedCombination(subclassingRequired, features); synchronized (this) { triggerRetransformation(features); } return subclassingRequired ? subclassEngine.mockClass(features) : features.mockedType; } Si la classe n’est pas finale : créé une sous-classe / implémentation
  228. @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain

    protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (classBeingRedefined == null || !mocked.contains(classBeingRedefined) || EXCLUDES.contains(classBeingRedefined)) { return null; } else { try { return byteBuddy.redefine(classBeingRedefined, ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)) .visit(new ParameterWritingVisitorWrapper(classBeingRedefined)) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.class).on(isVirtual() .and(not(isBridge().or(isHashCode()).or(isEquals()).or(isDefaultFinalizer()))) .and(not(isDeclaredBy(nameStartsWith("java.")).and(isPackagePrivate()))))) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForHashCode.class).on(isHashCode())) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForEquals.class).on(isEquals())) .make() .getBytes(); java.lang.instrumentation.ClassFileTransformer
  229. @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain

    protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (classBeingRedefined == null || !mocked.contains(classBeingRedefined) || EXCLUDES.contains(classBeingRedefined)) { return null; } else { try { return byteBuddy.redefine(classBeingRedefined, ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)) .visit(new ParameterWritingVisitorWrapper(classBeingRedefined)) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.class).on(isVirtual() .and(not(isBridge().or(isHashCode()).or(isEquals()).or(isDefaultFinalizer()))) .and(not(isDeclaredBy(nameStartsWith("java.")).and(isPackagePrivate()))))) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForHashCode.class).on(isHashCode())) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForEquals.class).on(isEquals())) .make() .getBytes(); } catch (Throwable throwable) { Est-ce que le type doit / peut être modifié ?
  230. @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain

    protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (classBeingRedefined == null || !mocked.contains(classBeingRedefined) || EXCLUDES.contains(classBeingRedefined)) { return null; } else { try { return byteBuddy.redefine(classBeingRedefined, ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)) .visit(new ParameterWritingVisitorWrapper(classBeingRedefined)) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.class).on(isVirtual() .and(not(isBridge().or(isHashCode()).or(isEquals()).or(isDefaultFinalizer()))) .and(not(isDeclaredBy(nameStartsWith("java.")).and(isPackagePrivate()))))) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForHashCode.class).on(isHashCode())) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForEquals.class).on(isEquals())) .make() .getBytes(); } catch (Throwable throwable) { Instructions de transformation
  231. return byteBuddy.redefine(classBeingRedefined, ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)) .visit(new ParameterWritingVisitorWrapper(classBeingRedefined)) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.class).on(isVirtual()

    .and(not(isBridge().or(isHashCode()) .or(isEquals()) .or(isDefaultFinalizer()))) .and(not(isDeclaredBy(nameStartsWith("java.")) .and(isPackagePrivate()))))) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForHashCode.class).on(isHashCode())) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForEquals.class).on(isEquals())) .make() .getBytes(); Indique à l’agent byte-buddy ou trouver le bytecode de la classe à modifier
  232. return byteBuddy.redefine(classBeingRedefined, ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)) .visit(new ParameterWritingVisitorWrapper(classBeingRedefined)) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.class).on(isVirtual()

    .and(not(isBridge().or(isHashCode()) .or(isEquals()) .or(isDefaultFinalizer()))) .and(not(isDeclaredBy(nameStartsWith("java.")) .and(isPackagePrivate()))))) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForHashCode.class).on(isHashCode())) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForEquals.class).on(isEquals())) .make() .getBytes(); API basé sur des visiteurs, comme ASM
  233. return byteBuddy.redefine(classBeingRedefined, ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)) .visit(new ParameterWritingVisitorWrapper(classBeingRedefined)) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.class).on(isVirtual()

    .and(not(isBridge().or(isHashCode()) .or(isEquals()) .or(isDefaultFinalizer()))) .and(not(isDeclaredBy(nameStartsWith("java.")) .and(isPackagePrivate()))))) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForHashCode.class).on(isHashCode())) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForEquals.class).on(isEquals())) .make() .getBytes(); Ré-insère le nom des paramètres Java 8 : -parameters
  234. return byteBuddy.redefine(classBeingRedefined, ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)) .visit(new ParameterWritingVisitorWrapper(classBeingRedefined)) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.class).on(isVirtual()

    .and(not(isBridge().or(isHashCode()) .or(isEquals()) .or(isDefaultFinalizer()))) .and(not(isDeclaredBy(nameStartsWith("java.")) .and(isPackagePrivate()))))) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForHashCode.class).on(isHashCode())) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForEquals.class).on(isEquals())) .make() .getBytes(); Instrumente les méthodes mockables
  235. return byteBuddy.redefine(classBeingRedefined, ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)) .visit(new ParameterWritingVisitorWrapper(classBeingRedefined)) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.class).on(isVirtual()

    .and(not(isBridge().or(isHashCode()) .or(isEquals()) .or(isDefaultFinalizer()))) .and(not(isDeclaredBy(nameStartsWith("java.")) .and(isPackagePrivate()))))) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForHashCode.class).on(isHashCode())) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForEquals.class).on(isEquals())) .make() .getBytes(); Donc pas • le constructeur • les méthodes privées hashcode / equals • les bridges … entre autres
  236. return byteBuddy.redefine(classBeingRedefined, ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)) .visit(new ParameterWritingVisitorWrapper(classBeingRedefined)) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.class).on(isVirtual()

    .and(not(isBridge().or(isHashCode()) .or(isEquals()) .or(isDefaultFinalizer()))) .and(not(isDeclaredBy(nameStartsWith("java.")) .and(isPackagePrivate()))))) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForHashCode.class).on(isHashCode())) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForEquals.class).on(isEquals())) .make() .getBytes(); Instrumente de manière spécifique les méthodes hashcode / equals
  237. return byteBuddy.redefine(classBeingRedefined, ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)) .visit(new ParameterWritingVisitorWrapper(classBeingRedefined)) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.class).on(isVirtual()

    .and(not(isBridge().or(isHashCode()) .or(isEquals()) .or(isDefaultFinalizer()))) .and(not(isDeclaredBy(nameStartsWith("java.")) .and(isPackagePrivate()))))) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForHashCode.class).on(isHashCode())) .visit(Advice.withCustomMapping() .bind(MockMethodAdvice.Identifier.class, identifier) .to(MockMethodAdvice.ForEquals.class).on(isEquals())) .make() .getBytes(); Renvoie le bytecode de la classe instrumentée
  238. Qui suis-je ? Back to basics Mockito 2 Nouvelles features

    Mockito 2 Changements internes Release delivery continuum Mockito 3 What’s next
  239. À la recherche d’un modèle de livraison / déploiement continu

  240. Tous les projets doivent délivrer.

  241. Quelle stratégie ?

  242. Quelle stratégie ? À quelle cadence ?

  243. Quelle stratégie ? À quelle cadence ? Sur quel canal

    ?
  244. Quelle stratégie ? À quelle cadence ? Sur quel canal

    ? Pour quels utilisateurs ?
  245. Dur dans un projet business

  246. Dur dans un projet open source

  247. Mockito a été actif lors des itérations pour la version

    1
  248. Puis une très longue période sans release

  249. Raisons multiples : Santé

  250. Raisons multiples : Santé Job

  251. Raisons multiples : Santé Job Temps

  252. Raisons multiples : Santé Job Temps Autres

  253. Comment être indépendant de la personne qui possède les identifiants

    ?
  254. Nous avons écrit un bot chargé de faire des releases

    et de les déployer
  255. Comment ça fonctionne ?

  256. git push upstream ✓ Not skipped ✓ Releasable branch ?

    ✓ Not a PR ✓ Previous and current jar are different [release/2.x] $ release/2.x
  257. ✓ Not skipped ✗ Releasable branch ? git push upstream

    [feature_#73] $ feature_#73
  258. ✓ Not skipped ✓ Releasable branch ? ✓ Not a

    PR ✓ Previous and current jar are different release/2.x PR #73 → release/2.x
  259. Ce bot et cette configuration de déploiements réponds aux questions

    suivantes
  260. Comment délivrer aux utilisateurs qui subissent un bug ?

  261. Comment délivrer aux utilisateurs les nouveautés ?

  262. Comment garantir que les scripts fonctionnent encore ?

  263. Comment favoriser le feedback sur un développement en cours ?

  264. Il y a cependant des problèmes

  265. Comment être garantir la compatibilité ?

  266. Les revues de code ont permi

  267. Les revues de code ont permi d’identifier des bugs

  268. Les revues de code ont permi d’identifier des bugs de

    (re) designer des APIs
  269. La fatigue des mises à jour pour les utilisateurs Marre

    de changer mon pom / gradle
  270. Ces questions ont été vivement débattues dans le ticket #618

  271. Nous souhaitons améliorer notre pipeline de delivery

  272. Qui suis-je ? Back to basics Mockito 2 Nouvelles features

    Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum
  273. Compilation avec Java 8 Accès aux types du JDK 8

  274. Réflexion sur une API basée sur les lambdas

  275. Amélioration du support JDK 9

  276. Amélioration du support JDK 9 Contrainte sur ASM 6

  277. Amélioration du support JDK 9 Contrainte sur ASM 6 Problème

    avec Jigsaw
  278. Dépréciation de when / then au profit de given /

    will
  279. Qui suis-je ? Back to basics Mockito 2 Nouvelles features

    Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum
  280. ??? https://speakerdeck.com/bric3/mockito-2-lyonjug-2017-01-26

  281. https://cdn1.macworld.co.uk/cmsdata/features/3608274/Terminalicon2_thumb800.png http://www.wpclipart.com/cartoon/signs/more_signs/warning_slippery.png.html https://crackberry.com/sites/crackberry.com/files/styles/large/public/topic_images/2013/ANDROID.png https://kotlinlang.org/assets/images/open-graph/kotlin_250x250.png https://raw.githubusercontent.com/JetBrains/intellij-community/master/platform/icons/src/nodes/class.svg https://raw.githubusercontent.com/JetBrains/intellij-community/master/platform/icons/src/fileTypes/text.svg https://image.freepik.com/free-icon/chronometer-quarter-hour-logistics_318-36128.jpg http://www.freeiconspng.com/free-images/direction-icon-png-4697 http://downloadicons.net/ekg-icon-87666 https://www.pinterest.com/pin/369084131932258329/

    Git, Jetbrains, Travis, Gradle, RxJava, Devoxx France, Cassandra, Kafka, Oracle Java Image sources
  282. Retiré

  283. 1. Qui suis-je ? 2. Back to basics a. Do

    you know mockito ? yes / no b. Do you know what is a mock ? yes / no 3. Nouvelles features de Mockito 2 a. Premier support de Java 8 b. Meilleur support des generics c. Qualité / Strict mocking d. API entre Java 6 et Java 8 e. Mockito.framework() / listeners f. Plugins g. Android support h. Classes finales i. Kotlin 4. Changements internes de Mockito 2 a. Bytebuddy (incompatibilité avec PowerMock) b. Mocking des classes finales 5. Release model a. Continuous delivery continuum 6. Pour Mockito 3 a. Extensions du support Java 8 b. Java 9 ready, dans les faits dépends de ASM6 c. Simplicity d. BDD api only ?
  284. Qui suis-je ? Back to basics Mockito 2 Nouvelles features

    Mockito 2 Changements internes Release model Mockito 3 What’s next
  285. Framework

  286. Autre nouveauté La mise en place d’une API publique autour

    du fonctionnement de Mockito
  287. La plupart des API publiques sont réparties dans les classes

    du package org.mockito
  288. Mockito / BDDMockito @Mock / @Spy / @InjectMocks ArgumentMatchers /

    AdditionalMatchers Answers / AdditionalAnswers
  289. L’outillage publique JUnit est dans org.mockito.junit

  290. Comment ouvrir l’API interne de Mockito ?

  291. Mise en place de Mockito.framework()

  292. Pour l’instant limité à l’ajout / retrait de listener

  293. Un listener doit implémenter une sous-interface de MockitoListener

  294. Exemple : lister les créations de mocks avec MockCreationListener

  295. Mockito.framework().addListener(...);

  296. Mockito.framework().addListener(new MockCreationListener());

  297. Mockito.framework().addListener(new MockCreationListener() { @Override public void onMockCreated(Object mock, MockCreationSettings settings)

    { } });
  298. Mockito.framework().addListener(new MockCreationListener() { @Override public void onMockCreated(Object mock, MockCreationSettings settings)

    { System.out.println("new mock created '" + mock + "'"); } });
  299. Mockito.framework().addListener(new MockCreationListener() { @Override public void onMockCreated(Object mock, MockCreationSettings settings)

    { System.out.println("new mock created '" + mock + "'"); } }); ... Mockito.mock(AwesomeType.class);
  300. new mock created 'Mock for AwesomeType, hashCode: 370869802'

  301. Cas plus concret : Tracker les mocks qui forcent la

    sérialisation
  302. Mockito.mock(AwesomeType.class, Mockito.withSettings() .serializable()); Mockito.mock(AwesomeType.class, Mockito.withSettings() .serializable(ACROSS_CLASSLOADERS)); Force le mock à

    supporter la sérialisation
  303. Créer une rule JUnit empêchant la création de mock serializable

  304. @ClassRule public static TestRule trackSerializableMock = new ScreamOnSerializableMock();

  305. public class ScreamOnSerializableMock implements TestRule { @Override public Statement apply(Statement

    base, Description description) { return ...; } }
  306. public class ScreamOnSerializableMock implements TestRule { @Override public Statement apply(Statement

    base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { base.evaluate(); } }; } }
  307. public class ScreamOnSerializableMock implements TestRule { @Override public Statement apply(Statement

    base, Description description) { MockCreationListener listener = new SerializableMockCreationListener(); return new Statement() { @Override public void evaluate() throws Throwable { base.evaluate(); } }; } }
  308. public class ScreamOnSerializableMock implements TestRule { @Override public Statement apply(Statement

    base, Description description) { MockCreationListener listener = new SerializableMockCreationListener(); return new Statement() { @Override public void evaluate() throws Throwable { Mockito.framework().addListener(listener); base.evaluate(); } }; } }
  309. public class ScreamOnSerializableMock implements TestRule { @Override public Statement apply(Statement

    base, Description description) { MockCreationListener listener = new SerializableMockCreationListener(); return new Statement() { @Override public void evaluate() throws Throwable { Mockito.framework().addListener(listener); base.evaluate(); } }; } } Ajoute le listener pour la méthode de test
  310. public class ScreamOnSerializableMock implements TestRule { @Override public Statement apply(Statement

    base, Description description) { MockCreationListener listener = new SerializableMockCreationListener(); return new Statement() { @Override public void evaluate() throws Throwable { try { Mockito.framework().addListener(listener); base.evaluate(); } finally { Mockito.framework().removeListener(listener); } } }; } } Important supprimer le listener
  311. Le listener enregistré peut réagir aux évènements de création de

    mock
  312. class SerializableMockCreationListener implements MockCreationListener { @Override public void onMockCreated(Object mock,

    MockCreationSettings settings) { } }
  313. class SerializableMockCreationListener implements MockCreationListener { @Override public void onMockCreated(Object mock,

    MockCreationSettings settings) { if(settings.isSerializable()) { } } }
  314. class SerializableMockCreationListener implements MockCreationListener { @Override public void onMockCreated(Object mock,

    MockCreationSettings settings) { if(settings.isSerializable()) { String msg = format("serializable mock created '%s', with mode=%s", mock, settings.getSerializableMode()); System.out.println(msg); throw new ProgrammerError(msg); } } }
  315. VerificationListener permet de réagir sur chaque appel à verify

  316. En fonction des besoins d’autres listeners seront ajoutés

  317. Mockito.framework()

  318. Kotlin

  319. Pour certains usages Mocker une classe finale peut-être utile

  320. Kotlin compile les classes avec le modifier final

  321. class PassengerNameRecord { fun isExpired(): Boolean { return ... }

    }
  322. class PassengerNameRecord { fun isExpired(): Boolean { return ... }

    } Classe finale
  323. class PassengerNameRecordTest { @Mock val pnr = PassengerNameRecord() @Rule @JvmField

    public val rulez = MockitoJUnit.rule(); @Test fun should_behave_like_that() { ... } }
  324. org.mockito.exceptions.base.MockitoException: Cannot mock/spy class com.github.bric3.PassengerNameRecord Mockito cannot mock/spy because :

    - final class
  325. mockito-extensions/org.mockito.plugins.MockMaker mock-maker-inline

  326. None
  327. Matchers Mockito peuvent retourner null

  328. Matchers Mockito peuvent retourner null Option : utiliser github.com/nhaarman/mockito-kotlin verify(myClass).setItems(argThat

    { size == 42 })
  329. CGLIB mockmaker

  330. Enhancer enhancer = new Enhancer(); Class<?>[] allMockedTypes = prepend(mockedType, interfaces);

    enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes)); enhancer.setUseFactory(true); if (mockedType.isInterface()) { enhancer.setSuperclass(Object.class); enhancer.setInterfaces(allMockedTypes); } else { enhancer.setSuperclass(mockedType); enhancer.setInterfaces(interfaces); } enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class}); enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS); if (mockedType.getSigners() != null) { enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES); } else { enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE); } enhancer.setSerialVersionUID(42L); try { return enhancer.createClass(); } catch (CodeGenerationException e) { // propagate with meaningful message } Configuration de CGLIB pour créer la classe du mock
  331. Enhancer enhancer = new Enhancer(); Class<?>[] allMockedTypes = prepend(mockedType, interfaces);

    enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes)); enhancer.setUseFactory(true); if (mockedType.isInterface()) { enhancer.setSuperclass(Object.class); enhancer.setInterfaces(allMockedTypes); } else { enhancer.setSuperclass(mockedType); enhancer.setInterfaces(interfaces); } enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class}); enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS); if (mockedType.getSigners() != null) { enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES); } else { enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE); } enhancer.setSerialVersionUID(42L); try { return enhancer.createClass(); } catch (CodeGenerationException e) { // propagate with meaningful message } Instance du créateur de classe de CGLIB
  332. Enhancer enhancer = new Enhancer(); Class<?>[] allMockedTypes = prepend(mockedType, interfaces);

    enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes)); enhancer.setUseFactory(true); if (mockedType.isInterface()) { enhancer.setSuperclass(Object.class); enhancer.setInterfaces(allMockedTypes); } else { enhancer.setSuperclass(mockedType); enhancer.setInterfaces(interfaces); } enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class}); enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS); if (mockedType.getSigners() != null) { enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES); } else { enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE); } enhancer.setSerialVersionUID(42L); try { return enhancer.createClass(); } catch (CodeGenerationException e) { // propagate with meaningful message } Mocked type + extra interfaces
  333. Enhancer enhancer = new Enhancer(); Class<?>[] allMockedTypes = prepend(mockedType, interfaces);

    enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes)); enhancer.setUseFactory(true); if (mockedType.isInterface()) { enhancer.setSuperclass(Object.class); enhancer.setInterfaces(allMockedTypes); } else { enhancer.setSuperclass(mockedType); enhancer.setInterfaces(interfaces); } enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class}); enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS); if (mockedType.getSigners() != null) { enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES); } else { enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE); } enhancer.setSerialVersionUID(42L); try { return enhancer.createClass(); } catch (CodeGenerationException e) { // propagate with meaningful message } Classloader chargé trouvé les bon classloader pour chaque type
  334. Enhancer enhancer = new Enhancer(); Class<?>[] allMockedTypes = prepend(mockedType, interfaces);

    enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes)); enhancer.setUseFactory(true); if (mockedType.isInterface()) { enhancer.setSuperclass(Object.class); enhancer.setInterfaces(allMockedTypes); } else { enhancer.setSuperclass(mockedType); enhancer.setInterfaces(interfaces); } enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class}); enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS); if (mockedType.getSigners() != null) { enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES); } else { enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE); } enhancer.setSerialVersionUID(42L); try { return enhancer.createClass(); } catch (CodeGenerationException e) { // propagate with meaningful message } Équivalent de @PostConstruct
  335. Enhancer enhancer = new Enhancer(); Class<?>[] allMockedTypes = prepend(mockedType, interfaces);

    enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes)); enhancer.setUseFactory(true); if (mockedType.isInterface()) { enhancer.setSuperclass(Object.class); enhancer.setInterfaces(allMockedTypes); } else { enhancer.setSuperclass(mockedType); enhancer.setInterfaces(interfaces); } enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class}); enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS); if (mockedType.getSigners() != null) { enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES); } else { enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE); } enhancer.setSerialVersionUID(42L); try { Hack pour gérer les mocks de classes ou les mocks d’interfaces
  336. Enhancer enhancer = new Enhancer(); Class<?>[] allMockedTypes = prepend(mockedType, interfaces);

    enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes)); enhancer.setUseFactory(true); if (mockedType.isInterface()) { enhancer.setSuperclass(Object.class); enhancer.setInterfaces(allMockedTypes); } else { enhancer.setSuperclass(mockedType); enhancer.setInterfaces(interfaces); } enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class}); enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS); if (mockedType.getSigners() != null) { enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES); } else { enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE); } enhancer.setSerialVersionUID(42L); try { return enhancer.createClass(); } catch (CodeGenerationException e) { // propagate with meaningful message } Type du callback que Mockito doit implémenter Type de callback NoOp
  337. Enhancer enhancer = new Enhancer(); Class<?>[] allMockedTypes = prepend(mockedType, interfaces);

    enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes)); enhancer.setUseFactory(true); if (mockedType.isInterface()) { enhancer.setSuperclass(Object.class); enhancer.setInterfaces(allMockedTypes); } else { enhancer.setSuperclass(mockedType); enhancer.setInterfaces(interfaces); } enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class}); enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS); if (mockedType.getSigners() != null) { enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES); } else { enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE); } enhancer.setSerialVersionUID(42L); try { return enhancer.createClass(); } catch (CodeGenerationException e) { // propagate with meaningful message } CGLIB ne gère pas les bridge méthodes
  338. Enhancer enhancer = new Enhancer(); Class<?>[] allMockedTypes = prepend(mockedType, interfaces);

    enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes)); enhancer.setUseFactory(true); if (mockedType.isInterface()) { enhancer.setSuperclass(Object.class); enhancer.setInterfaces(allMockedTypes); } else { enhancer.setSuperclass(mockedType); enhancer.setInterfaces(interfaces); } enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class}); enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS); if (mockedType.getSigners() != null) { enhancer.setNamingPolicy( NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES); } else { enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE); } enhancer.setSerialVersionUID(42L); try { Gestion du nommage pour les packages signés. Par exemple java.util
  339. Enhancer enhancer = new Enhancer(); Class<?>[] allMockedTypes = prepend(mockedType, interfaces);

    enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes)); enhancer.setUseFactory(true); if (mockedType.isInterface()) { enhancer.setSuperclass(Object.class); enhancer.setInterfaces(allMockedTypes); } else { enhancer.setSuperclass(mockedType); enhancer.setInterfaces(interfaces); } enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class}); enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS); if (mockedType.getSigners() != null) { enhancer.setNamingPolicy( NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES); } else { enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE); } enhancer.setSerialVersionUID(42L); try { Le nom de la classe contiendra EnhancedByMockitoWithCGLIB
  340. Enhancer enhancer = new Enhancer(); Class<?>[] allMockedTypes = prepend(mockedType, interfaces);

    enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes)); enhancer.setUseFactory(true); if (mockedType.isInterface()) { enhancer.setSuperclass(Object.class); enhancer.setInterfaces(allMockedTypes); } else { enhancer.setSuperclass(mockedType); enhancer.setInterfaces(interfaces); } enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class}); enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS); if (mockedType.getSigners() != null) { enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES); } else { enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE); } enhancer.setSerialVersionUID(42L); try { return enhancer.createClass(); } catch (CodeGenerationException e) { // propagate with meaningful message } Génère le bytecode et charge la classe dans SearchingClassLoader