Is it possible to inject mocks for testing purposes with AndroidAnnotations?
Asked Answered
T

2

8

I haven't found any examples on how to do this. I'm assuming it is not possible based on examples like this:

@Bean(MyImplementation.class)
MyInterface myInterface;

where the class to inject is already determined.

Thadeus answered 5/5, 2012 at 17:41 Comment(2)
Are you looking to test your class or the class generated by AndroidAnnotations?Morbidezza
I want to test a class that I write. I want to inject mocks into the class that I write for test code, and inject "real" objects for production.Thadeus
M
6

The question is, are you unit testing or integration testing?

If you are unit testing, I would suggest using mocks the old fashioned way, by using a setter and trying to test the Java code without the dependency injection framework involved. This will test your class in isolation and sidesteps a lot of complexity.

What I mean:

public class Test{

    ClassInTest inTest;
    MyInterface myInterface;

    @Before
    public void setup(){
         inTest = new ClassInTest();
         //or your favorite mocking frameowrk
         myInterface = EasyMock.createMock(MyInterface.class);  
         inTest.setMyInterface(myInterface);
    }

    @Test
    public void testMethod(){
        //...mocking test code
    }
}

Of course, testing Android Activities (and other extensions of Android) is difficult because of the exception throwing stubs and final classes/methods. This is where Robolectric comes in handy (and highly recommended) for instantiating/shadowing the Android API.

If you are integration testing you may want to take another approach. Personally, I would try not to mock during integration tests as I try to test the application as it would run in production. But, if you really want to mock, you could use a similar approach to unit testing and introduce a mock after you stand up your generated Activity class. Worth noting, you can perform integration tests directly on the hardware using frameworks like Robotium.

More to your question, I am not aware of any facilities of AndroidAnnotations specifically for injecting Mocks or introducing Mocks into the injected dependency tree of an application.

Morbidezza answered 14/5, 2012 at 16:14 Comment(2)
Thanks. I have gotten started using Robolectric for unit tests. In the example you gave above, are you saying to call the constructor of my class in the test method in order to bypass AndroidAnnotations during tests altogether?Thadeus
Calling the constructor (with Robolectic involved) just gets you an instance of the Activity. I wouldn't say this bypasses AndroidAnnotaions. But, if you test your class "MyActivity" (vs "MyActivity_") then you will not have the generated code responsible for DI from AA.Morbidezza
M
8

A complement to johncarl answer:

  • There's no way to tell AndroidAnnotations that you want to inject mocks instead of real objects, because it works at compile time, so the code must always be production ready.

  • I would recommend testing the generated activities, in complement with Robolectric. The annotations are adding behavior to your code, so you shouldn't test it as if there were no annotations.

  • Be careful of testing your activities behavior, not AndroidAnnotations' behavior. The framework already has tests of its own to check that the annotations work correctly :).

  • You can let AndroidAnnotations DI take place, and then reinject the mocked dependency. The fields have at least default scope, which mean they can be accessed from the same package, so you'd have to create the test in the same package as the activity.

    MyActivity_ activity = new MyActivity_();
    
    // myInterface gets injected 
    activity.onCreate(null);
    
    // you reinject myInterface
    activity.myInterface = Mockito.mock(MyInterface.class);
    
  • In AndroidAnnotations, dependencies are injected by calling MyImplementation_.getInstance_(). You could use runtime bytecode manipulation with a tool such as PowerMock to let the getInstance_() method of MyImplementation_ return a mock. This might require some initial work though, because you'd have to mix PowerMock test runner and Robolectric test runner.

Edit: I updated the documentation with content based on this question.

Masterly answered 19/5, 2012 at 15:18 Comment(5)
+1. That's interesting that you suggest testing the generated Activities (pt.2). I guess there are enough changes to the Activity to warrant this approach.Morbidezza
@Piwaï I'm not sure how the above mocking approach will work. If you have any methods annotated with AfterViews they will be executed as part of the Activity creation. If these methods have any dependencies they will fail - as you have not had a chance to mock them out yet. Any ideas on how to work around this?Toilworn
@Toilworn have you found any solution for this?Gourley
@SebastianRoth apologies on late reply - I never did find a satisfactory solution for this, and gave up trying some time ago.Toilworn
@Piwaï what do I do here #36064547Declassify
M
6

The question is, are you unit testing or integration testing?

If you are unit testing, I would suggest using mocks the old fashioned way, by using a setter and trying to test the Java code without the dependency injection framework involved. This will test your class in isolation and sidesteps a lot of complexity.

What I mean:

public class Test{

    ClassInTest inTest;
    MyInterface myInterface;

    @Before
    public void setup(){
         inTest = new ClassInTest();
         //or your favorite mocking frameowrk
         myInterface = EasyMock.createMock(MyInterface.class);  
         inTest.setMyInterface(myInterface);
    }

    @Test
    public void testMethod(){
        //...mocking test code
    }
}

Of course, testing Android Activities (and other extensions of Android) is difficult because of the exception throwing stubs and final classes/methods. This is where Robolectric comes in handy (and highly recommended) for instantiating/shadowing the Android API.

If you are integration testing you may want to take another approach. Personally, I would try not to mock during integration tests as I try to test the application as it would run in production. But, if you really want to mock, you could use a similar approach to unit testing and introduce a mock after you stand up your generated Activity class. Worth noting, you can perform integration tests directly on the hardware using frameworks like Robotium.

More to your question, I am not aware of any facilities of AndroidAnnotations specifically for injecting Mocks or introducing Mocks into the injected dependency tree of an application.

Morbidezza answered 14/5, 2012 at 16:14 Comment(2)
Thanks. I have gotten started using Robolectric for unit tests. In the example you gave above, are you saying to call the constructor of my class in the test method in order to bypass AndroidAnnotations during tests altogether?Thadeus
Calling the constructor (with Robolectic involved) just gets you an instance of the Activity. I wouldn't say this bypasses AndroidAnnotaions. But, if you test your class "MyActivity" (vs "MyActivity_") then you will not have the generated code responsible for DI from AA.Morbidezza

© 2022 - 2024 — McMap. All rights reserved.