Robolectric custom TestRunner not working when started from Gradle - java

I wanted to implement a custom Application class Shadow, to override a getInstance() method in it. I am using Robolectric 3.0 and have created a MyRobolectricTestRunner class, overriding the createClassLoaderConfig() method like this:
public class MyRobolectricTestRunner extends RobolectricTestRunner {
public MyRobolectricTestRunner(Class<?> testClass) throws InitializationError {
super(testClass);
}
#Override
public InstrumentationConfiguration createClassLoaderConfig() {
InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder();
builder.addInstrumentedClass(App.class.getName());
return builder.build();
}
}
The ShadowApp class looks like this:
#Implements(App.class)
public class ShadowApp{
#RealObject private static App instance;
public static void setAppInstance(App app){
instance = app;
}
#Implementation
public static App getInstance(){
return instance;
}
}
And the test which uses the Runner is annotated like this:
#RunWith(MyRobolectricTestRunner.class)
#Config(manifest=Config.NONE, shadows = {ShadowApp.class}, constants = BuildConfig.class, sdk = 21)
public class SomeShadowTest {
Now the problem is that when I run the test manually (hitting "Run..." for this single test class only), it passes without a problem, but when I use the Gradle "testDebug" task, the test fails as if the Shadow class was not used at all :(
I have tried changing the Runner parent class to RobolectricGradleTestRunner, but ended up in a dead end when it forced me to make the ShadowApp class extend a ShadowApplication class, which has getInstance() method as well... :(
Any tips on how to solve this issue?

I suggest you do not create shadow for application but instead use TestApplication class which Robolectric uses as test variant of the application class.
For this you just need to create class which extends your application class and has name Test and is placed in the root of your project - package of class same as package name of project.
See example below:
Assume, you package name is com.example.robolectric
// src/main/java/com/example/robolectric
public class YourAplication extends Application {
...
}
// src/test/java/com/example/robolectric
/**
* Robolectric uses class with name Test<ApplicationClassName> as test variant of the application
* class. We use test application for API class injection so we need test version of this class.
*/
public class TestYourAplication extends YourAplication {
...
}

Related

ServiceLoader not finding any services

I'm facing an issue where ServiceLoader does not find one provided service.
I have tested with regular project and the following sources:
// test/Tester.java
package test;
public interface Tester {
}
// test/TesterImpl.java
package test;
public class TesterImpl implements Tester {
}
// test/Runner.java
package test;
import java.util.ServiceLoader;
public class Runner {
public static void main(String[] args) {
var loader = ServiceLoader.load(Tester.class);
for (var tester : loader) {
System.out.println(tester);
}
}
}
// module-info.java
import test.Tester;
import test.TesterImpl;
module module {
uses Tester;
provides Tester with TesterImpl;
}
The above prints something akin to test.TesterImpl#1fb3ebeb, proving that it works as wanted.
The same fails to work when I try to use ServiceLoader.load(...) inside an AbstractProcessor that's run through maven-compiler-plugin. The processor returns an empty iterator instead. What is required to make it behave the same way in the annotation processor as it does in the case above?
THe solution to this issue was to specify a class loader - it appears that the annotation processor used a different classloader than the classes I was trying to load the services from. Solution is the following:
ServiceLoader.load(MyService.class, MyAnnotationProcessor.class.getClassLoader())

Dagger 2 instantiation on application component

I have a question with dagger2,
If I provide #Singleton with ApplicationComponent but don't instantiate the object using #Inject in some class. Does the object get instantiate or it will get instantiated when it is #Inject in some class?
For example, in below code, does test instantiated on main2?
#Singleton
public class Test {
#Inject
public Test() {
}
}
public class main() {
#Inject Test test;
public void start() {
DaggerComponent.create().inject(this);
}
}
public class main2() {
public void start() {
DaggerComponent.create().inject(this);
}
}
In above case , Test will be instantiated in class Main by the instance of the DaggerComponent in that class .
But in class Main2 , Test will not be instanciated unless an explicit #Inject annotation is marked on a property of type Test .
Also , note that in case above , if you need singleton instance of class Test in both class Main and Main2 , use the same DaggerComponent instance to inject Test object in both class . As you are separately instanciating DaggerComponent in both classes , you will get separate instances of class Test in Main and Main2.
If you want to know how dagger uses scopes behind the scenes, read the Dagger generated code . I have written an article on medium regarding how dagger scopes works internally. Follow this if you want to . How Dagger scopes work internally
It will get instantiated when it is injected in some class.
You can check the generated code by dagger for the inject(main2) method for your DaggerComponent class and it will be empty like this:
#Override
public void inject(main2 clazz) {}
Whereas the inject(main) method will have calls to inject the field (after creating an instance of it).

Make ApplicationContext dirty before and after test class

I have a particular class (let's say MyTest) in my Spring integration tests that is using PowerMock #PrepareForTest annotation on a Spring component: #PrepareForTest(MyComponent.class). This means that PowerMock will load this class with some modifications. The problem is, my #ContextConfiguration is defined on the superclass which is extended by MyTest, and the ApplicationContext is cached between different test classes. Now, if MyTest is run first, it will have the correct PowerMock version of MyComponent, but if not - the test will fail since the context will be loaded for another test (without #PrepareForTest).
So what I want to do is to reload my context before MyTest. I can do that via
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
But what if I also want to reload context after this test is done? So I will have clean MyComponent again without PowerMock modifications. Is there a way to do both BEFORE_CLASS and AFTER_CLASS?
For now I did it with the following hack:
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
on MyTest and then
/**
* Stub test to reload ApplicationContext before execution of real test methods of this class.
*/
#DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
#Test
public void aa() {
}
/**
* Stub test to reload ApplicationContext after execution of real test methods of this class.
*/
#DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD)
#Test
public void zz() {
}
I am wondering if there is a prettier way to do that?
As a side question, is it possible to reload only certain bean and not full context?
Is there a way to do both BEFORE_CLASS and AFTER_CLASS?
No, that is unfortunately not supported via #DirtiesContext.
However, what you're really saying is that you want a new ApplicationContext for MyTest that is identical to the context for the parent test class but only lives as long as MyTest. And... you don't want to affect the context cached for the parent test class.
So with that in mind, the following trick should do the job.
#RunWith(SpringJUnit4ClassRunner.class)
// Inherit config from parent and combine with local
// static Config class to create a new context
#ContextConfiguration
#DirtiesContext
public class MyTest extends BaseTests {
#Configuration
static class Config {
// No need to define any actual #Bean methods.
// We only need to add an additional #Configuration
// class so that we get a new ApplicationContext.
}
}
Alternative to #DirtiesContext
If you want to have a context dirtied both before and after a test class, you can implement a custom TestExecutionListener that does exactly that. For example, the following will do the trick.
import org.springframework.core.Ordered;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
public class DirtyContextBeforeAndAfterClassTestExecutionListener
extends AbstractTestExecutionListener {
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
testContext.markApplicationContextDirty(HierarchyMode.EXHAUSTIVE);
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
testContext.markApplicationContextDirty(HierarchyMode.EXHAUSTIVE);
}
}
You can then use the custom listener in MyTest as follows.
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.TestExecutionListeners.MergeMode;
#TestExecutionListeners(
listeners = DirtyContextBeforeAndAfterClassTestExecutionListener.class,
mergeMode = MergeMode.MERGE_WITH_DEFAULTS
)
public class MyTest extends BaseTest { /* ... */ }
As a side question, is it possible to reload only certain bean and not full context?
No, that is also not possible.
Regards,
Sam (author of the Spring TestContext Framework)

Guice: Using providers to get multiple instances:

I am trying to learn Guice for dependency Injection using Providers to create multiple instances of an object(Example from getting started guide on Guice website). how should I test this? Please advise.
The following is the module:
package testing;
import com.google.inject.AbstractModule;
public class BillingModule extends AbstractModule {
#Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
bind(BillingService.class).to(RealBillingService.class);
bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
}
}
The following is the class under test:
package testing;
import com.google.inject.Inject;
import com.google.inject.Provider;
public class RealBillingService implements BillingService {
private Provider<CreditCardProcessor> processorProvider;
private Provider<TransactionLog> transactionLogProvider;
#Inject
public RealBillingService(Provider<CreditCardProcessor> processorProvider,
Provider<TransactionLog> transactionLogProvider) {
this.processorProvider = processorProvider;
this.transactionLogProvider = transactionLogProvider;
}
public void chargeOrder() {
CreditCardProcessor processor = processorProvider.get();
TransactionLog transactionLog = transactionLogProvider.get();
/* use the processor and transaction log here */
processor.toString();
transactionLog.toString();
}
}
The following is the test class with main():
public class test {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new BillingModule());
BillingService billingService = injector.getInstance(BillingService.class);
billingService.chargeOrder();
}
}
Upon running this, I am expecting the output from the following toString methods to show up but am seeing nothing:
processor.toString();
transactionLog.toString();
What am i missing here?
Please advise,
thanks!
This happens because you just call toString without putting the resulting string anywhere (eg the call to System.out.println)
However providers are not intended to be used like that. You should not call Provider.get yourself: instead require the result of the provider, register your provider and let Guice do its job (you can also annotate methods in your modules with #Provides instead of defining provider classes)
By default providers are called each time a new instance of a certain class is required. Instances are not recycled unless you explicitly request it via using scopes (like the builtin Singleton)

Android Unit Tests with Dagger 2

I have an Android app that uses Dagger 2 for dependency injection. I am also using the latest gradle build tools that allow a build variant for unit testing and one for instrumentation tests. I am using java.util.Random in my app, and I want to mock this for testing. The classes I'm testing don't use any Android stuff, so they're just regular java classes.
In my main code I define a Component in a class that extends the Application class, but in the unit tests I'm not using an Application. I tried defining a test Module and Component, but Dagger won't generate the Component. I have also tried using the Component that I defined in my application and swapping the Module when I build it, but the application's Component doesn't have inject methods for my test classes. How can I provide a mock implementation of Random for testing?
Here's some sample code:
Application:
public class PipeGameApplication extends Application {
private PipeGame pipeGame;
#Singleton
#Component(modules = PipeGameModule.class)
public interface PipeGame {
void inject(BoardFragment boardFragment);
void inject(ConveyorFragment conveyorFragment);
}
#Override
public void onCreate() {
super.onCreate();
pipeGame = DaggerPipeGameApplication_PipeGame.create();
}
public PipeGame component() {
return pipeGame;
}
}
Module:
#Module
public class PipeGameModule {
#Provides
#Singleton
Random provideRandom() {
return new Random();
}
}
Base class for tests:
public class BaseModelTest {
PipeGameTest pipeGameTest;
#Singleton
#Component(modules = PipeGameTestModule.class)
public interface PipeGameTest {
void inject(BoardModelTest boardModelTest);
void inject(ConveyorModelTest conveyorModelTest);
}
#Before
public void setUp() {
pipeGameTest = DaggerBaseModelTest_PipeGameTest.create(); // Doesn't work
}
public PipeGameTest component() {
return pipeGameTest;
}
}
or:
public class BaseModelTest {
PipeGameApplication.PipeGame pipeGameTest;
// This works if I make the test module extend
// the prod module, but it can't inject my test classes
#Before
public void setUp() {
pipeGameTest = DaggerPipeGameApplication_PipeGame.builder().pipeGameModule(new PipeGameModuleTest()).build();
}
public PipeGameApplication.PipeGame component() {
return pipeGameTest;
}
}
Test Module:
#Module
public class PipeGameTestModule {
#Provides
#Singleton
Random provideRandom() {
return mock(Random.class);
}
}
This is currently impossible with Dagger 2 (as of v2.0.0) without some workarounds. You can read about it here.
More about possible workarounds:
How do you override a module/dependency in a unit test with Dagger 2.0?
Creating test dependencies when using Dagger2
You have hit the nail on the head by saying:
application's Component doesn't have inject methods for my test classes
So, to get around this problem we can make a test version of your Application class. Then we can have a test version of your module. And to make it all run in a test, we can use Robolectric.
1) Create the test version of your Application class
public class TestPipeGameApp extends PipeGameApp {
private PipeGameModule pipeGameModule;
#Override protected PipeGameModule getPipeGameModule() {
if (pipeGameModule == null) {
return super.pipeGameModule();
}
return pipeGameModule;
}
public void setPipeGameModule(PipeGameModule pipeGameModule) {
this.pipeGameModule = pipeGameModule;
initComponent();
}}
2) Your original Application class needs to have initComponent() and pipeGameModule() methods
public class PipeGameApp extends Application {
protected void initComponent() {
DaggerPipeGameComponent.builder()
.pipeGameModule(getPipeGameModule())
.build();
}
protected PipeGameModule pipeGameModule() {
return new PipeGameModule(this);
}}
3) Your PipeGameTestModule should extend the production module with a constructor:
public class PipeGameTestModule extends PipeGameModule {
public PipeGameTestModule(Application app) {
super(app);
}}
4) Now, in your junit test's setup() method, set this test module on your test app:
#Before
public void setup() {
TestPipeGameApp app = (TestPipeGameApp) RuntimeEnvironment.application;
PipeGameTestModule module = new PipeGameTestModule(app);
app.setPipeGameModule(module);
}
Now you can customize your test module how you originally wanted.
In my opinion you can approach this problem by looking at it from a different angle. You will easily be able to unit test your class by not depending upon Dagger for construction class under test with its mocked dependencies injected into it.
What I mean to say is that in the test setup you can:
Mock the dependencies of the class under test
Construct the class under test manually using the mocked dependencies
We don't need to test whether dependencies are getting injected correctly as Dagger verifies the correctness of the dependency graph during compilation. So any such errors will be reported by failure of compilation. And that is why manual creation of class under test in the setup method should be acceptable.
Code example where dependency is injected using constructor in the class under test:
public class BoardModelTest {
private BoardModel boardModel;
private Random random;
#Before
public void setUp() {
random = mock(Random.class);
boardModel = new BoardModel(random);
}
#Test
...
}
public class BoardModel {
private Random random;
#Inject
public BoardModel(Random random) {
this.random = random;
}
...
}
Code example where dependency is injected using field in the class under test (in case BoardModel is constructed by a framework):
public class BoardModelTest {
private BoardModel boardModel;
private Random random;
#Before
public void setUp() {
random = mock(Random.class);
boardModel = new BoardModel();
boardModel.random = random;
}
#Test
...
}
public class BoardModel {
#Inject
Random random;
public BoardModel() {}
...
}
If you are using dagger2 with Android, you can use app flavours for providing mocking resources.
See here for a demo of flavours in mock testing(without dagger):
https://www.youtube.com/watch?v=vdasFFfXKOY
This codebase has an example:
https://github.com/googlecodelabs/android-testing
In your /src/prod/com/yourcompany/Component.java
you provide your production components.
In your /src/mock/com/yourcompany/Component.java
you provide your mocking components.
This allows you create builds of your app with or without mocking.
It also allows parallel development (backend by one team, frontend app by another team), you can mock until api methods are avilable.
How my gradle commands look (its a Makefile):
install_mock:
./gradlew installMockDebug
install:
./gradlew installProdDebug
test_unit:
./gradlew testMockDebugUnitTest
test_integration_mock:
./gradlew connectedMockDebugAndroidTest
test_integration_prod:
./gradlew connectedProdDebugAndroidTest
I actually had the same issue and found a very simple solution.
This is not the best possible solution I think but it will solve your problem.
Create a similar class in your app module:
public class ActivityTest<T extends ViewModelBase> {
#Inject
public T vm;
}
Then, in your AppComponent add:
void inject(ActivityTest<LoginFragmentVM> activityTest);
Then you will be able to inject that in your test class.
public class HelloWorldEspressoTest extends ActivityTest<LoginFragmentVM> {
#Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class);
#Test
public void listGoesOverTheFold() throws InterruptedException {
App.getComponent().inject(this);
vm.email.set("1234");
closeSoftKeyboard();
}
}

Categories