Dagger 2 - unable to inject object - android

Im trying to do a very simple dependency injection in a Android app. I am using dagger 2 as a DI tool.
The issue is no injection is occuring:
here is my code:
//behold Motor.java in all its awe.
public class Motor {
private int rpm;
public Motor(){
this.rpm = 10; //default will be 10
}
public int getRpm(){
return rpm;
}
public void accelerate(int value){
rpm = rpm + value;
}
public void brake(){
rpm = 0;
}
}
and here is the Vehicle.java which utilities the motor class:
import javax.inject.Inject;
public class Vehicle {
private Motor motor;
#Inject
public Vehicle(Motor motor){
this.motor = motor;
}
public void increaseSpeed(int value){
motor.accelerate(value);
}
public void stop(){
motor.brake();
}
public int getSpeed(){
return motor.getRpm();
}
}
I then created a VehicleModule.java class to define my provider:
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
#Module
public class VehicleModule {
#Provides
#Singleton
Motor provideMotor(){
return new Motor();
}
#Provides #Singleton
Vehicle provideVehicle(){
return new Vehicle(new Motor());
}
}
I then i have a interface component annotated, defined like this:
import javax.inject.Singleton;
import Modules.VehicleModule;
import dagger.Component;
#Singleton
#Component(modules = {VehicleModule.class})
public interface VehicleComponent {
Vehicle provideVehicle();
}
and here is my Android mainactivity class that should be injected but its not, can anyone help:
import javax.inject.Inject;
public class MainActivity extends ActionBarActivity {
#Inject
Vehicle vehicle;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(this, String.valueOf(vehicle.getSpeed()), Toast.LENGTH_SHORT).show();
}
}
im getting a null pointer exception on vehicle:
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int com.example.uen229.myapplication.Vehicle.getSpeed()' on a null object reference
by the way my gradle dependencies look like this:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.google.dagger:dagger:2.0'
}

here is my Android mainactivity class that should be injected but its not
You're expecting magical things happen when you annotate something with #Inject. While magical things will happen, it's not that magical. You will need to do that yourself, by instantiating the component implementations that Dagger generated.
You can do this in a couple of ways, I will describe two.
First, in your MainActivity's onCreate:
private Vehicle vehicle; // Note, no #Inject annotation
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
VehicleComponent vehicleComponent = DaggerVehicleComponent.create();
this.vehicle = vehicleComponent.provideVehicle();
Toast.makeText(this, String.valueOf(vehicle.getSpeed()), Toast.LENGTH_SHORT).show();
}
In this case, you create an instance of VehicleComponent, implemented by Dagger, and fetch the Vehicle instance from it. The vehicle field is not annotated by #Inject. This has the advantage that the field can be private, which is a good thing to want.
Secondly, if you do want Dagger to inject your fields, you need to add an inject method to your VehicleComponent:
#Singleton
#Component(modules = {VehicleModule.class})
public interface VehicleComponent {
Vehicle provideVehicle();
void inject(MainActivity mainActivity);
}
In your MainActivity class, you call inject(this), which will fill the vehicle field:
#Inject
Vehicle vehicle; // Note, package-local
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
VehicleComponent vehicleComponent = DaggerVehicleComponent.create();
vehicleComponent.inject(this);
Toast.makeText(this, String.valueOf(vehicle.getSpeed()), Toast.LENGTH_SHORT).show();
}
This brings a bit of extra configuration, but is sometimes necessary.
I like the first method however.
As a final comment, let's have a look at your VehicleModule, and really use the power of Dagger.
Instead of using the module to create the instances yourself, you can make Dagger to that for you. You've already annotated the Vehicle constructor with #Inject, so Dagger will know to use this constructor. However, it needs an instance of Motor, which it doesn't know of. If you add an #Inject annotation to the constructor of Motor as well, and annotate the Motor class with #Singleton, you can get rid of the VehicleModule altogether!
For example:
#Singleton
public class Motor {
private int rpm;
#Inject // Just an annotation to let Dagger know how to retrieve an instance of Motor.
public Motor(){
this.rpm = 10; //default will be 10
}
public int getRpm(){
return rpm;
}
public void accelerate(int value){
rpm = rpm + value;
}
public void brake(){
rpm = 0;
}
}
Your Vehicle class:
#Singleton
public class Vehicle {
private Motor motor;
#Inject
public Vehicle(Motor motor){
this.motor = motor;
}
public void increaseSpeed(int value){
motor.accelerate(value);
}
public void stop(){
motor.brake();
}
public int getSpeed(){
return motor.getRpm();
}
}
You can now safely delete the VehicleModule class, and remove the reference to it in your VehicleComponent.

you are missing the following steps
you have create the module like this in application class
public class D2EApplication extends Applicaiton {
public static D2EComponent component(Context context) {
return ((D2EBaseApplication) context.getApplicationContext()).component;
}
public final static class DaggerComponentInitializer {
public static D2EComponent init(D2EBaseApplication app) {
return DaggerD2EComponent.builder()
.systemServicesModule(new VehicleModule(app))
.build();
}
}
}
and inject it to the activity
D2EApplication.component(this).inject(this);

Related

How to mock sharedpreferences for android instrumentation tests?

I have a preference util class to store and retrieve the data in shared preferences in a single place.
Prefutils.java:
public class PrefUtils {
private static final String PREF_ORGANIZATION = "organization";
private static SharedPreferences getPrefs(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
private static SharedPreferences.Editor getEditor(Context context) {
return getPrefs(context).edit();
}
public static void storeOrganization(#NonNull Context context,
#NonNull Organization organization) {
String json = new Gson().toJson(organization);
getEditor(context).putString(PREF_ORGANIZATION, json).apply();
}
#Nullable public static Organization getOrganization(#NonNull Context context) {
String json = getPrefs(context).getString(PREF_ORGANIZATION, null);
return new Gson().fromJson(json, Organization.class);
}
}
Sample code showing PrefUtils usage in LoginActivity.java:
#Override public void showLoginView() {
Organization organization = PrefUtils.getOrganization(mActivity);
mOrganizationNameTextView.setText(organization.getName());
}
List of androidTestCompile dependencies in build.gradle:
// Espresso UI Testing dependencies.
androidTestCompile "com.android.support.test.espresso:espresso-core:$project.ext.espressoVersion"
androidTestCompile "com.android.support.test.espresso:espresso-contrib:$project.ext.espressoVersion"
androidTestCompile "com.android.support.test.espresso:espresso-intents:$project.ext.espressoVersion"
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2:'
src/androidTest/../LoginScreenTest.java
#RunWith(AndroidJUnit4.class) #LargeTest public class LoginScreenTest {
#Rule public ActivityTestRule<LoginActivity> mActivityTestRule =
new ActivityTestRule<>(LoginActivity.class);
#Before public void setUp() throws Exception {
when(PrefUtils.getOrganization(any()))
.thenReturn(HelperUtils.getFakeOrganization());
}
}
The above code to return fakeOrganization was not working, running the tests on login activity results in NullPointerException in line mOrganizationNameTextView.setText(organization.getName()); defined in the above LoginActivity.java class.
How to solve the above issue?
Approach-1:
Expose SharedPreference with application scope using Dagger2 and use it like #Inject SharedPreferences mPreferences in activity/fragment.
Sample code using the above approach to save(write) a custom preference:
SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(PREF_ORGANIZATION, mGson.toJson(organization));
editor.apply();
To read a custom preference:
String organizationString = mPreferences.getString(PREF_ORGANIZATION, null);
if (organizationString != null) {
return mGson.fromJson(organizationString, Organization.class);
}
If you use it like above it results in breaking the DRY principle, since the code will be repeated in multiple places.
Approach-2:
This approach is based on the idea of having a separate preference class like StringPreference/ BooleanPreference which provides wrapper around the SharedPreferences code to save and retrieve values.
Read the below posts for detailed idea before proceeding with the solution:
Persist your data elegantly: U2020 way by #tasomaniac
Espresso 2.1: ActivityTestRule by chiuki
Dagger 2 + Espresso 2 + Mockito
Code:
ApplicationModule.java
#Module public class ApplicationModule {
private final MyApplication mApplication;
public ApplicationModule(MyApplication application) {
mApplication = application;
}
#Provides #Singleton public Application provideApplication() {
return mApplication;
}
}
DataModule.java
#Module(includes = ApplicationModule.class) public class DataModule {
#Provides #Singleton public SharedPreferences provideSharedPreferences(Application app) {
return PreferenceManager.getDefaultSharedPreferences(app);
}
}
GsonModule.java
#Module public class GsonModule {
#Provides #Singleton public Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return gsonBuilder.create();
}
}
ApplicationComponent.java
#Singleton #Component(
modules = {
ApplicationModule.class, DataModule.class, GsonModule.class
}) public interface ApplicationComponent {
Application getMyApplication();
SharedPreferences getSharedPreferences();
Gson getGson();
}
MyApplication.java
public class MyApplication extends Application {
#Override public void onCreate() {
initializeInjector();
}
protected void initializeInjector() {
mApplicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
}
}
OrganizationPreference.java
public class OrganizationPreference {
public static final String PREF_ORGANIZATION = "pref_organization";
SharedPreferences mPreferences;
Gson mGson;
#Inject public OrganizationPreference(SharedPreferences preferences, Gson gson) {
mPreferences = preferences;
mGson = gson;
}
#Nullable public Organization getOrganization() {
String organizationString = mPreferences.getString(PREF_ORGANIZATION, null);
if (organizationString != null) {
return mGson.fromJson(organizationString, Organization.class);
}
return null;
}
public void saveOrganization(Organization organization) {
SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(PREF_ORGANIZATION, mGson.toJson(organization));
editor.apply();
}
}
Wherever you need the preference just inject it using Dagger #Inject OrganizationPreference mOrganizationPreference;.
For androidTest, I'm overriding the preference with a mock preference. Below is my configuration for android tests:
TestDataModule.java
public class TestDataModule extends DataModule {
#Override public SharedPreferences provideSharedPreferences(Application app) {
return Mockito.mock(SharedPreferences.class);
}
}
MockApplication.java
public class MockApplication extends MyApplication {
#Override protected void initializeInjector() {
mApplicationComponent = DaggerTestApplicationComponent.builder()
.applicationModule(new TestApplicationModule(this))
.dataModule(new TestDataModule())
.build();
}
}
LoginScreenTest.java
#RunWith(AndroidJUnit4.class) public class LoginScreenTest {
#Rule public ActivityTestRule<LoginActivity> mActivityTestRule =
new ActivityTestRule<>(LoginActivity.class, true, false);
#Inject SharedPreferences mSharedPreferences;
#Inject Gson mGson;
#Before public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
MyApplication app = (MyApplication) instrumentation.getTargetContext().getApplicationContext();
TestApplicationComponent component = (TestApplicationComponent) app.getAppComponent();
component.inject(this);
when(mSharedPreferences.getString(eq(OrganizationPreference.PREF_ORGANIZATION),
anyString())).thenReturn(mGson.toJson(HelperUtils.getFakeOrganization()));
mActivityTestRule.launchActivity(new Intent());
}
}
Make sure you have dexmaker mockito added in build.gradle
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2:'
Unfortunately Mockito cannot perform what you are looking for on its own. You have two options, one is to use Power Mock and the other is to change Prefutils into a normal class and instead use a Dependency Injection Framework.
Power Mock
Nice and simple, this will let you mock static methods, check out this SO post for details. On the downside it may result in other issues based on the comments in that SO post.
Dependency Injection Approach (my original answer)
You are trying to write a UI test with some of the behavior of the application "mocked". Mockito is built to let you write Unit tests where you test a specific object (or group of objects) and mock some of their behavior.
You can see some examples of how mockito is used in these tests (1, 2). None of them test the UI, instead they instantiate an object "stub"/"mock" some if its behavior and then test the rest.
To achieve what you want you will instead need a dependency injection framework. This allows you to change the "implementation" of some of your application based on whether you are running the actual application or a test.
The details of how you mock behavior of your classes/objects varies from framework to framework. This blog post goes over how to use Dagger 2 with Mockito and espresso you can apply the same approach for your tests. It also has links to presentations that give more background on dagger 2.
If you don't like dagger 2 then you can also checkout RoboGuice and Dagger. Just note, I do not think butter-knife will fit your needs as it doesn't support injection of Pojos.

Constructor injection Dagger 2

I can't inject dependencies through the constructor and I'm sure that I'm doing something wrong.
I have the following:
public class Water {
#Inject
public Water() {}
#Override
public String toString() { return "Water + ";}
}
public class Heater {
#Inject
public Heater() {}
#Override
public String toString() {return " heater";}
}
public class Aeropress {
Water mWater;
Heater mHeater;
#Inject
public Aeropress(Water water, Heater heater) {
mWater = water;
mHeater = heater;
}
#Override
public String toString() {
return mWater.toString() + mHeater.toString();
}
}
public class LoginActivity extends AppCompatActivity{
#Inject Aeropress aeropress;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("* 69 * LoginActivity *", "onCreate " + aeropress);
}
The code in activity prints null so dagger doesn't inject anything. Any idea how to solve this without using #provide ? What am I missing ?
In order to do this you have to do 2 things:
1. Declare a component with an inject method
#Component
public interface AeropressComponent {
void inject(LoginActivity aeropress);
}
2. Build the dagger component in your activity and inject it
public class LoginActivity extends AppCompatActivity{
#Inject Aeropress aeropress;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerAeropressComponent.builder().build().inject(this);
/*DaggerAeropressComponent.create().inject(this);*/ //or this
Log.e("* 69 * LoginActivity *", "onCreate " + aeropress);
}
After these modifications, it works like a charm without even creating a #Module class. The logic how I understand this and the reason why you need a component is that in order for dagger to inject a new instance into aeropress, it needs the container(the activity) where a reference of aeropress can be found. Also I just remembered now that the reason why #Inject fields can't be private is that dagger makes direct assignment between the reference and the created instance, in my example it does the following
LoginActivity.aeropress = Factory.createAeropress();
So without creating the component with the inject method, it can't know where to put the instance created with Factory.createAeropress();
If anyone can give me a better solution I'll mark the answer

Is this the correct way of adding parammeters to constructor in Dagger 2?

Context
Recenty I started investigating about dependency injection and Dagger 2. It looks a pretty good library but it seems a bit confusing to me. There are some situations in which I don't know exactly how to proceed.
What have I tried
I have created a simple Android app that creates a Client and its Dependency and do some (dummy) work. These are the classes:
Client.java
public class Client {
private Dependency dep;
#Inject
public Client(Dependency dep) {
this.dep = dep;
}
public void work() {
System.out.println("Client working");
dep.doWork();
}
}
Dependency.java
public class Dependency {
#Inject
public Dependency() {
}
public void doWork() {
System.out.println("Dependency working");
}
}
Following some tutorials I created a couple of Module classes:
DependencyModule.java
#Module
public class DependencyModule {
#Provides
Dependency provideDependency() {
return new Dependency();
}
}
ClientModule.java
#Module
public class ClientModule {
#Provides
Client provideClient(Dependency dep) {
return new Client(dep);
}
}
And also the Component interface:
#Component(modules = {ClientModule.class})
public interface ClientComponent {
Client provideClient();
}
This works fine. From my activity I can do the following and it works:
ClientComponent clientComp = DaggerClientComponent
.builder()
.clientModule(new ClientModule())
.build();
Client client = clientComp.provideClient();
client.work();
Problem
I understand how to inject dependencies in a client (at least I think so). But how I add parameters into the constructor of a client/dependency?
I mean, what if I would wanted to add some int parameters to my objects? Something as simple as this:
Client.java
public class Client {
int id;
Dependency dep;
#Inject
public Client(int id, Dependency dep) {
this.id = id;
this.dep = dep;
}
public void work() {
System.out.println("id: " + id + " Client working");
dep.doWork();
}
}
Dependency.java
public class Dependency {
private int id;
#Inject
public Dependency(int id) {
this.id = id;
}
public void doWork() {
System.out.println("id: " + id + " Dependency working");
}
}
NOTE:
The following code is what I've tried. So I'm not sure about its correctness.
So, as the objects has new parameters in their constructor the Modules have to change:
DependencyModule.class
public class DependencyModule {
#Provides
Dependency provideDependency() {
return new Dependency(id);
}
}
ClientModule.class
#Module
public class ClientModule {
#Provides
Client provideClient(int id, Dependency dep) {
return new Client(id, dep);
}
}
Question
How do I use that new Modules? I haven't found a way to pass the id to that methods. The only way I get it to work is by passing it in the Module constructor and removing it from the provide method. This way:
#Module
public class ClientModule {
private int id;
public ClientModule(int id) {
this.id = id;
}
#Provides
Client provideClient(Dependency dep) {
return new Client(id, dep);
}
}
Same approach in the DependencyModule.java.
This way, adding the DependencyModule.class in the ClientComponent interface I can do something like:
ClientComponent clientComp = DaggerClientComponent
.builder()
.clientModule(new ClientModule(clientId))
.dependencyModule(new DependencyModule(dependencyId))
.build();
Client client = clientComp.provideClient();
client.work();
Is that the correct way of doing that?
Is there a better way of getting the same effect?
Am I committing crimes against DI principle?
There are two basic ways to get Dagger to provide an instance of a class:
Add #Inject to a constructor, and put the class's dependencies in as constructor arguments.
Add a #Provides-annotated method to a #Module-annotated class, and install that module into your #Component.
You only need to use one method for each class. So in your first example, Client and Dependency are fine as is; you don't also need ClientModule and DependencyModule.
Once you add the int dependency, now you do need a module, because there's no class to #Inject. The module just needs to provide that int, so something like this would work:
#Module
public class ClientIdModule {
private final clientId;
public ClientIdModule(int clientId) {
this.clientId = clientId;
}
#Provides
static int clientId() {
return clientId;
}
}
Now if you install ClientIdModule into your component, you'll be able to get a Client which has the right ID, and its Dependency will as well.

How can I replace Activity scoped dependencies with mocks using Dagger2

I have a scoped dependency in my Activity and I want to test that activity with some mocks. I have read about different approach that suggest to replace Application component with a test component during the test, but what I want is to replace the Activity component.
For example, I want to test the Activity against mock presenter in my MVP setup.
I believe that replacing component by calling setComponent() on Activity will not work, because Activity dependencies already injected via field injection, so during the test, real object will be used.
How can I resolve this issue? What about Dagger1? Is it has the same issue?
Injecting the Component
First, you create a static class to act as a factory for your Activity. Mine looks a little like this:
public class ActivityComponentFactory {
private static ActivityComponentFactory sInstance;
public static ActivityComponentFactory getInstance() {
if(sInstance == null) sInstance = new ActivityComponentFactory();
return sInstance;
}
#VisibleForTesting
public static void setInstance(ActivityComponentFactory instance) {
sInstance = instance;
}
private ActivityComponentFactory() {
// Singleton
}
public ActivityComponent createActivityComponent() {
return DaggerActivityComponent.create();
}
}
Then just do ActivityComponentFactory.getInstance().createActivityComponent().inject(this); inside your Activities.
For testing, you can replace the factory in your method, before the Activity is created.
Providing mocks
As #EpicPandaForce's answer makes clear, doing this the officially-supported way currently involves a lot of boilerplate and copy/pasted code. The Dagger 2 team need to provide a simpler way of partially overriding Modules.
Until they do though, here's my unnoficial way: Just extend the module.
Let's say you want to replace your ListViewPresenter with a mock. Say you have a PresenterModule which looks like this:
#Module #ActivityScope
public class PresenterModule {
#ActivityScope
public ListViewPresenter provideListViewPresenter() {
return new ListViewPresenter();
}
#ActivityScope
public SomeOtherPresenter provideSomeOtherPresenter() {
return new SomeOtherPresenter();
}
}
You can just do this in your test setup:
ActivityComponentFactory.setInstance(new ActivityComponentFactory() {
#Override
public ActivityComponent createActivityComponent() {
return DaggerActivityComponent.builder()
.presenterModule(new PresenterModule() {
#Override
public ListViewPresenter provideListViewPresenter() {
// Note you don't have to use Mockito, it's just what I use
return Mockito.mock(ListViewPresenter.class);
}
})
.build();
}
});
...and it just works!
Note that you don't have to include the #Provides annotation on the #Override method. In fact, if you do then the Dagger 2 code generation will fail.
This works because the Modules are just simple factories - the generated Component classes take care of caching instances of scoped instances. The #Scope annotations are used by the code generator, but are irrelevant at runtime.
You cannot override modules in Dagger2 [EDIT: you can, just don't specify the #Provides annotation on the mock), which would obviously be the proper solution: just use the builder().somethingModule(new MockSomethingModule()).build() and be done with it!
If you thought mocking is not possible, then I would have seen two possible solutions to this problem. You can either use the modules to contain a pluggable "provider" that can have its implementation changed (I don't favor this because it's just too verbose!)
public interface SomethingProvider {
Something something(Context context);
}
#Module
public class SomethingModule {
private SomethingProvider somethingProvider;
public SomethingModule(SomethingProvider somethingProvider) {
this.somethingProvider = somethingProvider;
}
#Provides
#Singleton
public Something something(Context context) {
return somethingProvider.something(context);
}
}
public class ProdSomethingProvider implements SomethingProvider {
public Something something(Context context) {
return new SomethingImpl(context);
}
}
public class TestSomethingProvider implements SomethingProvider {
public Something something(Context context) {
return new MockSomethingImpl(context);
}
}
SomethingComponent somethingComponent = DaggerSomethingComponent.builder()
.somethingModule(new SomethingModule(new ProdSomethingProvider()))
.build();
Or you can bring the provided classes and injection targets out into their own "metacomponent" interface, which your ApplicationComponent and your TestApplicationComponent extend from.
public interface MetaApplicationComponent {
Something something();
void inject(MainActivity mainActivity);
}
#Component(modules={SomethingModule.class})
#Singleton
public interface ApplicationComponent extends MetaApplicationComponent {
}
#Component(modules={MockSomethingModule.class})
#Singleton
public interface MockApplicationComponent extends MetaApplicationComponent {
}
The third solution is to just extend the modules like in #vaughandroid 's answer. Refer to that, that is the proper way of doing it.
As for activity scoped components... same thing as I mentioned here, it's just a different scope, really.
I've found the following post that solves the problem:
http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html
You need first to allow to modify the component of the activity:
#Override public void onCreate() {
super.onCreate();
if (component == null) {
component = DaggerDemoApplication_ApplicationComponent
.builder()
.clockModule(new ClockModule())
.build();
}
}
public void setComponent(DemoComponent component) {
this.component = component;
}
public DemoComponent component() {
return component;
}
And modify it in the test case
#Before
public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
DemoApplication app
= (DemoApplication) instrumentation.getTargetContext().getApplicationContext();
TestComponent component = DaggerMainActivityTest_TestComponent.builder()
.mockClockModule(new MockClockModule())
.build();
app.setComponent(component);
component.inject(this);
}

Unit test static method with dependency

I have not much experience in unit testing, especially with Mockito and now I have encountered the following situation.
class A {
void setField(String obj) {
}
Object execute() {
throw new RuntimeException("Meh!");
}
}
class B {
//function to be tested
static Object someMethod() {
A a = new A();
a.setField("test");
Object response = a.execute();
//logic here
return response;
}
}
class BTest() {
A aInstance = mock(A.class);
#Test
public void test_someMethod_when_exec_returns_X() {
when(aInstance.execute()).thenReturns("X");// doesn’t work
assertTrue("X", B.someMethod());
}
}
I want to test the someMethod static method when a.execute() returns specific value.
I know, I can create a mock object of A and pass it to someMethod function, which is not a good solution as I should change the signature of someMethod.
What is the correct solution in this case?
If you check out PowerMockito's documentation you'll realize that the following is what you need:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.whenNew;
// execute the test with the appropriate runner
#RunWith(PowerMockRunner.class)
// prepare B for instrumentation so we can hack stuff inside
#PrepareForTest(B.class)
public class MyTest {
#Test
public void bShouldCallA() throws Exception {
// create a mock for A and configure its behaviour
A aMock = mock(A.class);
when(aMock.execute()).thenReturn("X");
// make sure that when A's constructor is called in the static method, the mock above is returned
whenNew(A.class).withNoArguments().thenReturn(aMock);
// do the actual invocation
Object actualResult = B.someMethod();
// check result and interactions
assertEquals("X", actualResult);
verify(aMock).setField("test");
}
}
As I mentioned, the PowerMockito doesn't work in android, you can just mock android object with that. And here comes engineering solution :)
Factory class to create object A.
public class AFactory {
static private AFactory sInsntance = new AFactory();
public static AFactory createObject() {
return sInsntance.createInternally();
}
protected TMMethodBuilder createInternally() {
return new A();
}
//This function is only for testing, in order to inject factory
#Deprecated
public static void setFactory(AFactory mock) {
sInsntance = mock;
}
}
And create object A:
A a = AFactory.createObject();
In Test project extend AFactory and override createInternally() method to return mocked object.
public class AFactoryTest extends AFactory {
private static A a = mock(A.class);
#Override
protected TMMethodBuilder createInternally() {
return a;
}
}
So in test class just do the following:
factory = new AFactoryTest();
a = factory.createInternally();
AFactory.setFactory(factory);
//
when(..).thenReturn();

Categories

Resources