Share
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.
Brice Dutheil
Transcript
340 slides
-
Qui suis-je ? Back to basics Mockito 2 Nouvelles features
Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum -
Qui suis-je ? Back to basics Mockito 2 Nouvelles features
Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum -
Qui suis-je ? Back to basics Mockito 2 Nouvelles features
Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum -
Bien sûr J’ai tous leurs goodies Non Vous pouvez répéter
la question ? Qui connait Mockito ? -
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 -
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()); -
@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()); -
@RunWith(MockitoJUnitRunner.Silent.class) public class WithARunnerTest { } public class WithARuleTest {
@Rule public MockitoRule rulez = MockitoJUnit.rule(); } -
@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()); -
@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 -
Qui suis-je ? Back to basics Mockito 2 Nouvelles features
Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum -
public Long returns_a_long() { } mock.returns_a_long() public List<Thing> returns_a_list() {
} mock.returns_a_list() Retourne une collection vide -
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 -
Stream et Optional ne sont donc pas disponible dans l’API
publique de Mockito -
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 -
Avec Java 6 ou Java 7, il est possible d’utiliser
l’ancienne API -
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 -
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 -
L’ancienne API est dépréciée car elle n’a plus d’intérêt avec
l’inférence de type -
Pour faciliter la transition : Utiliser la nouvelle API avec
des cast en Java 6/7 -
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"); -
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 -
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 -
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 -
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 -
Java 8 permet d’écrire du code concret dans les méthodes
d’interface connues sous le nom default methods -
@Rule public MockitoRule rulez = MockitoJUnit.rule(); @Mock private Consumer<String> consumer1;
@Mock private Consumer<String> consumer2; -
@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"); } -
@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"); } -
@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 -
@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 -
@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 -
@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); }; } -
À ce jour les mocks d’interface n’invoquent pas le comportement
concret des default methods -
Un spy est un mock partiel. Par défaut invoque les
méthodes concrètes mais permet de modifier le comportement -
Créer un spy à partir de classes abstraites ou d’interfaces
est maintenant possible -
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"); -
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é -
La pratique du TDD permet de maintenir la qualité du
code de production -
public class Tested { private Dep1 dep1; private Dep2 dep2;
private Dep3 dep3; public void behavior() { dep1.sub_behaviour(); dep2.sub_behaviour(); dep3.sub_behaviour(); } } -
@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(); //... } -
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 -
public class Tested { private Dep1 dep1; private Dep2 dep2;
private Dep3 dep3; public void behavior() { dep3.sub_behaviour(); } } Feature supprimée -
public class Tested { private Dep3 dep3; public void behavior()
{ dep3.sub_behaviour(); } } -
[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) -
@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(); //... } -
@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 -
@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 -
@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 -
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. -
Le tout premier mécanisme consiste à créer dans ses sources
une classe org.mockito.configuration.MockitoConfiguration -
Lorsque Mockito identifie cette classe dans le classpath, il tient
compte des nouveaux réglages -
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 ...; } } -
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 ...; } } -
package org.mockito.configuration; public class MockitoConfiguration extends DefaultMockitoConfiguration implements IMockitoConfiguration {
@Override public boolean cleansStackTrace() { return ...; } @Override public boolean enableClassCache() { return ...; } } -
Demande de Google (Jessie Wilson), pour permettre à Mockito de
fonctionner sur la VM Dalvik -
Pourquoi ne pas utiliser ServiceLoader ? Ajouté dans le niveau
d’API 9 (~2011) -
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 -
MockMaker, génère un mock InstantiatorProvider, instancie un mock AnnotationEngine, gère
les annotations StackTraceCleanerProvider, néttoie les stack traces -
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 -
/** * Serves as a marker for dummy mocks, when
processed creates * a mock using {@code Mcokito.mock(FieldType.class)} */ public @interface Dummy { } -
/** * 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; -
public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>
clazz, Object testInstance) { } } -
public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>
clazz, Object testInstance) { testInstance.getClass().getDeclaredFields() } } -
public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>
clazz, Object testInstance) { Arrays.stream(testInstance.getClass().getDeclaredFields()) } } -
public class DummyAnnotationEngine implements AnnotationEngine { @Override public void process(Class<?>
clazz, Object testInstance) { Arrays.stream(testInstance.getClass().getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Dummy.class)) } } -
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 -> { }); } } -
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())); }); } } -
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())); }); } } -
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) { } }); } } -
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); } }); } } -
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); } }); } } -
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); } }); } } -
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 -
public class DummyAnnotationEngine implements AnnotationEngine { AnnotationEngine internalEngine = new
InjectingAnnotationEngine(); @Override public void process(Class<?> clazz, Object testInstance) { internalEngine.process(clazz, testInstance); } } API interne -
Une évolution de l’API publique permettra d’obtenir le plugin par
défaut de Mockito -
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' // ... } -
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.+' // ... } -
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' // ... } -
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 -
Depuis la version 2.6.1 nous avons une alternative à dexmaker
: mockito-android -
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' // ... } -
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.+' // ... } -
public final class FinalClass { public String foo() { return
"foo"; } } FinalClass finalClass = mock(FinalClass.class); given(finalClass.foo()).willReturn("bar"); -
public final class FinalClass { public String foo() { return
"foo"; } } FinalClass finalClass = mock(FinalClass.class); given(finalClass.foo()).willReturn("bar"); assertThat(proxy.foo()).isEqualTo("bar"); -
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 -
Même avec les classes du JDK ! À quelques exceptions
près Class, String, … -
Toujours questionner s’il est sage de mocker un type qui
n’est pas sous notre contrôle -
Vous ne voulez pas mocker Pattern, classe finale, son comportement
change entre chaque JDK -
Vous ne voulez pas mocker HashMap, classe non finale, son
comportement change entre chaque JDK -
Sur votre CI vos tests peuvent être verts mais le
code exécuté en production peut être erroné -
Qui suis-je ? Back to basics Mockito 2 Nouvelles features
Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum -
Un doudou pour les bébés Le remplaçant de CGLIB Qu’est-ce
que byte-buddy ? -
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 -
… relativement avancée, elle demande un certain niveau de connaissance
sur la JVM Mais pas autant que ASM -
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, -
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 -
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 -
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 -
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. -
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 -
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 -
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é -
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 -
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) -
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 -
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 -
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 -
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 -
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 -
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; -
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 -
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 -
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 -
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 -
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 -
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 -
Type à mocker Classe du mock extends C mock =
mock(C.class); -
@RunWith(PowerMockRunner.class) @PrepareForTest({C.class}) Classloader PowerMock 010010111010… Reload la classe Classe finale
Supprime final Nouveau classloader -
@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 -
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; -
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 -
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 -
JVM C mock = mock(C.class); Classe finale Agent mockito Transforme
/ instrumente la classe Indique à l’agent de modifier C -
JVM C mock = mock(C.class); Classe finale Agent mockito Ajoute
l’intercepteur mockito dans la classe C -
@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; } -
@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. -
@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 -
@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); -
@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 -
@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 -
@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é ? -
@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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
Qui suis-je ? Back to basics Mockito 2 Nouvelles features
Mockito 2 Changements internes Release delivery continuum Mockito 3 What’s next -
Quelle stratégie ? À quelle cadence ? Sur quel canal
? Pour quels utilisateurs ? -
Nous avons écrit un bot chargé de faire des releases
et de les déployer -
git push upstream ✓ Not skipped ✓ Releasable branch ?
✓ Not a PR ✓ Previous and current jar are different [release/2.x] $ release/2.x -
✓ Not skipped ✗ Releasable branch ? git push upstream
[feature_#73] $ feature_#73 -
✓ Not skipped ✓ Releasable branch ? ✓ Not a
PR ✓ Previous and current jar are different release/2.x PR #73 → release/2.x -
Les revues de code ont permi d’identifier des bugs de
(re) designer des APIs -
La fatigue des mises à jour pour les utilisateurs Marre
de changer mon pom / gradle -
Qui suis-je ? Back to basics Mockito 2 Nouvelles features
Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum -
Qui suis-je ? Back to basics Mockito 2 Nouvelles features
Mockito 2 Changements internes Mockito 3 What’s next Release delivery continuum -
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 -
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 ? -
Qui suis-je ? Back to basics Mockito 2 Nouvelles features
Mockito 2 Changements internes Release model Mockito 3 What’s next -
Autre nouveauté La mise en place d’une API publique autour
du fonctionnement de Mockito -
La plupart des API publiques sont réparties dans les classes
du package org.mockito -
Mockito / BDDMockito @Mock / @Spy / @InjectMocks ArgumentMatchers /
AdditionalMatchers Answers / AdditionalAnswers -
Mockito.framework().addListener(new MockCreationListener() { @Override public void onMockCreated(Object mock, MockCreationSettings settings)
{ System.out.println("new mock created '" + mock + "'"); } }); -
Mockito.framework().addListener(new MockCreationListener() { @Override public void onMockCreated(Object mock, MockCreationSettings settings)
{ System.out.println("new mock created '" + mock + "'"); } }); ... Mockito.mock(AwesomeType.class); -
Cas plus concret : Tracker les mocks qui forcent la
sérialisation -
public class ScreamOnSerializableMock implements TestRule { @Override public Statement apply(Statement
base, Description description) { return ...; } } -
public class ScreamOnSerializableMock implements TestRule { @Override public Statement apply(Statement
base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { base.evaluate(); } }; } } -
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(); } }; } } -
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(); } }; } } -
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 -
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 -
class SerializableMockCreationListener implements MockCreationListener { @Override public void onMockCreated(Object mock,
MockCreationSettings settings) { } } -
class SerializableMockCreationListener implements MockCreationListener { @Override public void onMockCreated(Object mock,
MockCreationSettings settings) { if(settings.isSerializable()) { } } } -
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); } } } -
class PassengerNameRecordTest { @Mock val pnr = PassengerNameRecord() @Rule @JvmField
public val rulez = MockitoJUnit.rule(); @Test fun should_behave_like_that() { ... } } -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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