mlinhard / mockito

Automatically exported from code.google.com/p/mockito
0 stars 0 forks source link

enhancement: add @CustomMock to inject object not created by mockito (not @Mock, @Spy) into @InjectMocks #323

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago

Hi,

I need to inject some custom objects into @InjectMocks fields, but with objects 
that should not be created with Mockito, these objects are custom.
I can not use @Mock neither @Spy for theses objects because they are final, 
dynamic proxies, or anonymous inner classes...

The main motivation for using some custom mocks in the tested object is that I 
can reuse/subclass an existing object, without needing to call 
"when().thenReturn()" thousands of times to teach the mock, as the custom mock 
already have the code for answering simple cases, and anyway this is not the 
part I am interrested in mocking for the current test.

I propose to use a new annotation in Mockito API :@CustomMock

@Target( { FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomMock {
}

The required change in Mockito is extremely light : it only add 1 annotation 
class, and 1 line of change in MockScanner to handle the case similar to @Mock, 
but with @CustomMock;

+++ 
b/src/org/mockito/internal/configuration/injection/scanner/MockScanner.java Wed 
Feb 29 22:06:25 2012 +0100
@@ -1,5 +1,6 @@
 package org.mockito.internal.configuration.injection.scanner;

+import org.mockito.CustomMock;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
@@ -74,6 +75,7 @@
     private boolean isAnnotatedByMockOrSpy(Field field) {
         return null != field.getAnnotation(Spy.class)
                 || null != field.getAnnotation(Mock.class)
+                || null != field.getAnnotation(CustomMock.class)
                 || null != field.getAnnotation(MockitoAnnotations.Mock.class);
     }

I put a patch as an attachment file

Additional note: 
A typical use case is when mixing Spring tests and Mockito tests (which may or 
may not be real unit-test, but can become integration-test when connecting to a 
database...) : I already have 95% of the dependencies with Spring working, I 
want to change only 1 or 2 mock fields for my test.. This allows me to do 
integration test very quickly by mocking the 5%, and letting spring answer for 
the other 95%.
example:

class MyTest extends MySpringBaseTestCase {

  @InjectMocks sut;

  @Autowired // <= instanciate by Spring
  @CustomMock  // ... then inject by Mockito into 'sut'
  private MySpringDependency sutSpringDependency;

  @Mock OtherDependency sutDependency2;

  @Before setupMokito() { ...}

  @Test public void test() {
     // no need to call "when(sutSpringDependency.foo(xyz)).thenReturn(...);" ... spring already knows how to perform foo(xyz)

  }
}

I have coded it and it work great, but outside Mockito code, so I introduced 
different package name for my annotation, and I have copy&paste most on 
MockitoAnnotations.initMocks() into my CustomMockitoAnnotations.initMock(), 
which is not pleasant.

Original issue reported on code.google.com by arnaud.n...@gmail.com on 29 Feb 2012 at 9:47

Attachments:

GoogleCodeExporter commented 9 years ago
What do you think of issue 290  on that matter ?
The fact is, it's not that difficult to add this feature, but we have to be 
careful with the API, we are not an injection framework. The proposed 
annotation name @Collaborator has an interesting meaning, in the sense that it 
does not make people think of it as DI.

So you *might* be able to write that :
@RunWith(MockitoJUnitRunner.class)
public class SomeTest {
    @InjectMocks Echoer echoer;
    @Collaborator Observer theObserver = makeObserver();

    private Observer makeObserver() {
        Observer mock = mock(Observer.class);
        doThrow(Exception.class).when(mock).update(any(Observable.class), any());
        return mock;
    }

    @Test(expected = Exception.class) public void name() {
        echoer.echo("yay");
    }

    private static class Echoer extends Observable {
        private Echoer(Observer observer) { this.addObserver(observer); }

        public void echo(String msg) {
            setChanged();
            notifyObservers(msg);
        }
    }
}

Original comment by brice.du...@gmail.com on 1 Mar 2012 at 11:56

GoogleCodeExporter commented 9 years ago

just a few comments on http://code.google.com/p/mockito/issues/detail?id=290

1) This is indeed exactly the same issue, so close this one as a 'duplicate'

2) in the comments of issue 290, someone is talking about studying how to do 
it... but in my issue, I actually gave a patch as an attachment that does it! 
try the patch, it is few lines of code...

3) I don't like the class name "Collaborator", and I find "CustomMock" more 
appropriate and more intuitive. Is it only for my way of programming ? Let me 
explain why I prefer this name:
  - "Collaborator" has no special meaning. It means someone working with another, but not by pleasure (this was the name gave to the few french people working with german enemies during world war 2)...
  - "CustomMock" means that the annoted object is considered as a "mock", and more specifically is a "custom" "mock". Just like in java naming convention, a BufferedOutputStream is a "stream", for "output", using "buffer" ... always read for right to left to say what it is, and what it does. 
  - So @InjectMocks  is naturally able to inject "Mock", all kinds of mocks:  "Mock", "CustomMock", and "AnyOtherMock".
  - I would even consider more intuitive to have "Spy" annotation class beeing renamed "SpyMock"... because just as here, a "SpyMock" object is considered a "Mock" for dependency (@InjectMocks), that internally use "Spy" stubbing.
  - By having a naming convention (namespace) for all annotations to always prefix or suffix by "Mock", it is more easy to remember the mockito annotation names, and the fact that theses classes are from Mockito package. In spring Roo for example, you have such a convention: all annotations starts with @Roo.

4) Last but not least remarks...
I have found another solution since yesterday for injecting my dependency 
objects, that use both Spring and Mockito, and I find that it is also an 
elegant way of mixing test concerns with spring for the integration test part 
(using real DAO), and mockito for the specific test part. As You said, Mockito 
is not a dependency framework, so let's use spring for what it really does 
well..
You might add this sample in the wiki web site.
Here is my test:

// almost all my tests in mymodule inherits from this Junit spring settings
@RunWith(SpringJUnit4Runner.class)
@ContextLocation("classpath:/mymodule-applicationContext-junit.xml")
public class SpringBaseTestCase extends Assert {

   @Autowired private ApplicationContext testApplicationContext;

   protected void autowireBeanForSpringTest(Object testObj) {
      testApplicationContext.getAutowireCapableBeanFactory().autowireBean(testObj);
   }
}

public class XyzTest extends SpringBaseTestCase {
   @InjectMocks 
   protected XyzService sut;

   @Mock
   protected Dependency1 dep1;
   @Spy 
   protected Dependency2 dep2 = new Dependency2Impl();
   @CustomMock
   protected Dependency3 dep3 = new Dependency3Impl();

   @Before public void setup() {
      //step 1: create the system under test (the object is local to the test, not created by spring or mockito)
      sut = new XyzServiceImpl();
      // step 2: autowire using spring the object annoted dependencies, but using beans from the spring junit xml context (not the real production xml context)
      autowireBeanForSpringTest(this);
      // step 3: overwrite some of the sut dependencies with mockito mocks, created by @Mock,@Spy,@CustomMock
      MockitoAnnotations.initMocks(this);
      // step 4: manually overwrite some of the sut settings if ever
      sut.setXyzMode(true);
      sut.setXyzSetting(1234);
   }
}

Original comment by arnaud.n...@gmail.com on 1 Mar 2012 at 8:07

GoogleCodeExporter commented 9 years ago
Hi Arnaud,

On point 2: you should read "...studying how to do it (properly and API-wise)" 
;)
Because I agree there is now real difficulty to change some line of code here 
and there, but imagine someone want his own other annotation, then what... we 
clearly can't do that forever.

On point 3: I disagree with you, because @Collaborator wasn't intended for 
mocks at first and because @CustomMock semantic could mean mocks customized by 
other means than Mockito, even non Mockito mocks. This question is the real 
"study on how to do it" ;)

On point 4: It works, but I'm still not fond of this CustomMock and Spring 
autowiring, due to the nature of the test, maybe I need some time to appreciate 
the idea.

Original comment by brice.du...@gmail.com on 1 Mar 2012 at 11:30

GoogleCodeExporter commented 9 years ago
I agree with Brice, the CustomMock name is not accurate because: 1. it may not 
be a mock at all, 2. 'custom' is confusing as mockito does not have a notion of 
'custom' mock so it reads to me as some kind of mock from a different mocking 
tool that is adapted to mockito.

We may still iterate and brainstorm on a good name but so far Collaborator > 
CustomMock :)

Original comment by szcze...@gmail.com on 12 Mar 2012 at 2:40

GoogleCodeExporter commented 9 years ago
Oh by the way I did a typo error in my above comment on point 2
=> I agree there is *no* real difficulty ...

Original comment by brice.du...@gmail.com on 12 Mar 2012 at 3:32

GoogleCodeExporter commented 9 years ago

Original comment by brice.du...@gmail.com on 3 Sep 2012 at 10:00