How to switch my Worker to TestWorker with Hilt? - android-testing

I am doing end-to-end test which use WorkManager in my Application. My DelegatingWorkerFactory() is hilt injected and creates the different factories, which in turn create the different ListenableWorkers in my app.
For testing, I would like to bind to these workers to the test version i.e. TestListenableWorker(). What is the best way to do this using Hilt?
Appreciate there could be XY problem here and there might be a different approach to achieve this. Essentially I just want to seamlessly switch to TestListenableWorkers with hilt injection.
#Singleton
class MyDelegatingWorkerFactory #Inject constructor(
myRepository: MyRepository
): DelegatingWorkerFactory() {
init {
addFactory(WorkerGetDataFactory(myRepository))
addFactory(WorkerOnReceiverFactory(myRepository))
addFactory(WorkerUpdateStateFactory(myRepository))
}
}
class WorkerGetDataFactory(
private val myRepository: MyRepository
): WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters,
): ListenableWorker? { /*<---- how can I use Hilt to implement TestListenableWorker instance*/
return when (workerClassName) {
WorkerGetData::class.java.name ->
WorkerGetData(myRepository, appContext, workerParameters, )
else ->
null
}
}
}

Related

How can I inject with Guice my api into dataflow jobs without needed to be serializable?

This question is a follow on after such a great answer Is there a way to upload jars for a dataflow job so we don't have to serialize everything?
This made me realize 'ok, what I want is injection with no serialization so that I can mock and test'.
Our current method requires our apis/mocks to be serialiable BUT THEN, I have to put static fields in the mock because it gets serialized and deserialized creating a new instance that dataflow uses.
My colleague pointed out that perhaps this needs to be a sink and that is treated differently? <- We may try that later and update but we are not sure right now.
My desire is from the top to replace the apis with mocks during testing. Does someone have an example for this?
Here is our bootstrap code that does not know if it is in production or inside a feature test. We test end to end results with no apache beam imports in our tests meaning we swap to any tech if we want to pivot and keep all our tests. Not only that, we catch way more integration bugs and can refactor without rewriting tests since the contracts we test are customer ones we can't easily change.
public class App {
private Pipeline pipeline;
private RosterFileTransform transform;
#Inject
public App(Pipeline pipeline, RosterFileTransform transform) {
this.pipeline = pipeline;
this.transform = transform;
}
public void start() {
pipeline.apply(transform);
pipeline.run();
}
}
Notice that everything we do is Guice Injection based so the Pipeline may be direct runner or not. I may need to modify this class to pass things through :( but anything that works for now would be great.
The function I am trying to get our api(and mock and impl to) with no serialization is thus
private class ValidRecordPublisher extends DoFn<Validated<PractitionerDataRecord>, String> {
#ProcessElement
public void processElement(#Element Validated<PractitionerDataRecord>element) {
microServiceApi.writeRecord(element.getValue);
}
}
I am not sure how to pass in microServiceApi in a way that avoid serialization. I would be ok with delayed creation as well after deserialization using guice Provider provider; with provider.get() if there is a solution there too.
Solved in such a way that mocks no longer need static or serialization anymore by one since glass bridging the world of dataflow(in prod and in test) like so
NOTE: There is additional magic-ness we have in our company that passes through headers from service to service and through dataflow and that is some of it in there which you can ignore(ie. the RouterRequest request = Current.request();). so for anyone else, they will have to pass in projectId into getInstance each time.
public abstract class DataflowClientFactory implements Serializable {
private static final Logger log = LoggerFactory.getLogger(DataflowClientFactory.class);
public static final String PROJECT_KEY = "projectKey";
private transient static Injector injector;
private transient static Module overrides;
private static int counter = 0;
public DataflowClientFactory() {
counter++;
log.info("creating again(usually due to deserialization). counter="+counter);
}
public static void injectOverrides(Module dfOverrides) {
overrides = dfOverrides;
}
private synchronized void initialize(String project) {
if(injector != null)
return;
/********************************************
* The hardest part is this piece since this is specific to each Dataflow
* so each project subclasses DataflowClientFactory
* This solution is the best ONLY in the fact of time crunch and it works
* decently for end to end testing without developers needing fancy
* wrappers around mocks anymore.
***/
Module module = loadProjectModule();
Module modules = Modules.combine(module, new OrderlyDataflowModule(project));
if(overrides != null) {
modules = Modules.override(modules).with(overrides);
}
injector = Guice.createInjector(modules);
}
protected abstract Module loadProjectModule();
public <T> T getInstance(Class<T> clazz) {
if(!Current.isContextSet()) {
throw new IllegalStateException("Someone on the stack is extending DoFn instead of OrderlyDoFn so you need to fix that first");
}
RouterRequest request = Current.request();
String project = (String)request.requestState.get(PROJECT_KEY);
initialize(project);
return injector.getInstance(clazz);
}
}
I suppose this may not be what you're looking for, but your use case makes me think of using factory objects. They may depend on the pipeline options that you pass (i.e. your PipelineOptions object), or on some other configuration object.
Perhaps something like this:
class MicroserviceApiClientFactory implements Serializable {
MicroserviceApiClientFactory(PipelineOptions options) {
this.options = options;
}
public static MicroserviceApiClient getClient() {
MyPipelineOptions specialOpts = options.as(MySpecialOptions.class);
if (specialOpts.getMockMicroserviceApi()) {
return new MockedMicroserviceApiClient(...); // Or whatever
} else {
return new MicroserviceApiClient(specialOpts.getMicroserviceEndpoint()); // Or whatever parameters it needs
}
}
}
And for your DoFns and any other execution-time objects that need it, you would pass the factory:
private class ValidRecordPublisher extends DoFn<Validated<PractitionerDataRecord>, String> {
ValidRecordPublisher(MicroserviceApiClientFactory msFactory) {
this.msFactory = msFactory;
}
#ProcessElement
public void processElement(#Element Validated<PractitionerDataRecord>element) {
if (microServiceapi == null) microServiceApi = msFactory.getClient();
microServiceApi.writeRecord(element.getValue);
}
}
This should allow you to encapsulate the mocking functionality into a single class that lazily creates your mock or your client at pipeline execution time.
Let me know if this matches what you want somewhat, or if we should try to iterate further.
I have no experience with Guice, so I don't know if Guice configurations can easily pass the boundary between pipeline construction and pipeline execution (serialization / submittin JARs / etc).
Should this be a sink? Maybe, if you have an external service, and you're writing to it, you can write a PTransform that takes care of it - but the question of how you inject various dependencies will remain.

Calling injected services from class instances

I'm writing an Angular2 app. Consider a class Cat which I instantiate many instances of, which has an API method to write itself out:
class Cat {
constructor(private name: string) { }
public write() {
Api.write(this); // <== how do I do this?
}
}
The Cat#write method needs to access an API service wrapped around Http:
#Injectable()
class Api {
constructor(private http: Http) { }
write(data) {
this.http.post(...);
}
}
The (perhaps trivially simple) problem I have is how to access API#write from within my Cat class. Since I need the Cat constructor to pass in the name, I can't use it for injection. So how do I make the API#write available to it? Is there some way to access the singleton instance of Api? In that case, who would be in charge of instantiating the singleton instance?
I played around with a static API class but this obviously doesn't work, since injection is into instances, not static classes.
What basic design pattern am I missing here?
I've been struggling with this for a while as well. I did come up with sort of a solution, but it feels hacky.
I use a singleton AppInjector instance. It is set only once, in the constructor of AppComponent (the bootstrapped component). It is used to resolve dependencies for objects to create.
This AppInjector looks something like this:
export class AppInjector {
private static _INJECTOR: Injector;
public static set INSTANCE(injector: Injector) {
if (!this._INJECTOR) {
this._INJECTOR = injector;
}
}
public static get INSTANCE() : Injector {
return this._INJECTOR;
}
}
And the contructor of the AppComponent looks like this:
constructor(injector: Injector) {
AppInjector.INSTANCE = injector;
}
Now if you have added your Api in the bootstrap function of your application:
bootstrap(AppComponent, [Api]);
You will be able to get this singleton from within the Cat instance like this:
class Cat {
public get _api() : Api {
return AppInjector.INSTANCE.get(Api);
}
constructor(private name: string) { }
public write() {
this._api.write(this);
}
}
Again, this by no means feels like the right solution, and probably against a lot of guidelines in Angular2, but I understand your problem, and would also like a better solution than this, but it works fine now for as it is, and I can happily continue coding

Bind list of objects using Guice + Kotlin

I'm writing a JavaFX application in Kotlin with the following controller definition:
class MainController {
#Inject private lateinit var componentDescriptors: List<ComponentDescriptor>
/* More code goes here */
}
I'm using Guice for Dependency management. And I'm trying to inject the list of class instances loaded via java.util.ServiceLoader. My problem is to define a binding that will inject the list of loaded object instances into the declared field. I tried annotation based provisioning:
internal class MyModule: AbstractModule() {
override fun configure() { }
#Provides #Singleton
fun bindComponentDescriptors(): List<ComponentDescriptor> =
ServiceLoader.load(ComponentDescriptor::class.java).toList()
}
and multibinding extension (switched List to Set in field definition of corse):
internal class MyModule: AbstractModule() {
override fun configure() {
val componentDescriptorBinder = Multibinder.newSetBinder(binder(), ComponentDescriptor::class.java)
ServiceLoader.load(ComponentDescriptor::class.java).forEach {
componentDescriptorBinder.addBinding().toInstance(it)
}
}
}
but both of these approaches leads to the same error:
No implementation for java.util.List<? extends simpleApp.ComponentDescriptor> was bound.
while locating java.util.List<? extends simpleApp.ComponentDescriptor>
for field at simpleApp.MainController.componentDescryptors(MainController.kt:6)
while locating simpleApp.MainController
1 error
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1042)
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1001)
at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
at com.gluonhq.ignite.guice.GuiceContext.getInstance(GuiceContext.java:46)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:929)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:971)
at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
... 12 more
I'm starting to suspect that it somehow related to Kotlin gerenic variance and Guice strict type checking. But I don't know how to declare the binding so Guice will know what to inject into this field.
Yes, it happens because of variance but there's a way to make it work.
class MainController {
#JvmSuppressWildcards
#Inject
private lateinit var componentDescriptors: List<ComponentDescriptor>
}
By default Kotlin generates List<? extends ComponentDescriptor> signature for the componentDescriptors field. The #JvmSuppressWildcards makes it generate a simple parameterized signature List<ComponentDescriptor>.
#Michael gives the correct answer and explanation. Here's an example of one strategy for unit testing a Set multibinding for those that like to test their modules:
class MyModuleTest {
#JvmSuppressWildcards
#Inject
private lateinit var myTypes: Set<MyType>
#Before fun before() {
val injector = Guice.createInjector(MyModule())
injector.injectMembers(this)
}
#Test fun multibindings() {
assertNotNull(myTypes)
assertTrue(myTypes.iterator().next() is MyType)
}
}
#Michael comment is working. If you want to do the injection in constructor, you need do something like
class MainController #Inject consturctor(
private var componentDescriptors: List<#JvmSuppressWildcards ComponentDescriptor>
) {}

Dagger Modules with constructor arguments?

In Guice, I had full control over when Modules were constructed, and used some Modules with constructor arguments that I installed.
In Dagger however, the method of referencing other Modules is through the #Module includes annotation, and doesn't present me with the same method of creating Modules to install.
Is it possible to create a sane ObjectGraph from multiple Modules that have constructor arguments? Especially one that will work with dagger-compiler, and not run into a cyclical graph?
If you have multiple modules with that use the same object then maybe you should separate that object into its own module. For example, a lot of the Modules use the Application context so I have the following module:
#Module
public class ContextModule {
private final Context mContext;
public ContextModule(Context context) {
mContext = context;
}
#Provides
public Context provideContext() {
return mContext;
}
}
So now in other modules when when I need a context object I just include the module.
For example:
#Module(entryPoints = { MyFragment.class }, includes = { ContextModule.class })
public class ServicesModule {
#Provides
public LocationManager provideLocationManager(Context context) {
return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
}
#Provides
public Geocoder provideGeocoder(Context context) {
return new Geocoder(context);
}
}
Then when I construct the object graph I end up with only one module that takes the application context as its argument.
ObjectGraph.create() takes a variable list of modules (Varargs) so you are able to do this:
ObjectGraph objectGraph = ObjectGraph.create(new ProductionModule(context), new OverridingTestModule());
Take a look at Dagger's InjectionTest.java (see test "moduleOverrides" there): https://github.com/square/dagger/blob/master/core/src/test/java/dagger/InjectionTest.java

Google Guice and varying injections at runtime

I'd like to vary the injected implementations based on something that's not known until runtime. Specifically, I'd like my app to operate as different versions where the "version" is not determined until a request is executing. Also, the "version" could vary per request.
After reading the docs it seems that I could implement a providers in cases where I need to choose an implementation at runtime based on the "version". Additionally, I could roll my own on top of juice.
Is implementing a provider the best way to go in this scenario? I'd like to know if there is a best practice or if anyone else out there has tried to use Guice to tackle this problem.
Thanks for any help!
-Joe
I think that if the version can be known only at runtime, you must provide the versioned "services" manually with custom provider. Possibly something like this:
#Singleton
public abstract class VersionedProvider<T, V> {
private Map<V, T> objects;
T get(V version) {
if (!objects.containsKey(version)) {
objects.put(version, generateVersioned(version));
}
return objects.get(version);
}
// Here everything must be done manually or use some injected
// implementations
public abstract T generateVersioned(V version);
}
public class MyRuntimeServiceModule extends AbstractModule {
private final String runTimeOption;
public ServiceModule(String runTimeOption) {
this.runTimeOption = runTimeOption;
}
#Override protected void configure() {
Class<? extends Service> serviceType = option.equals("aServiceType") ?
AServiceImplementation.class : AnotherServiceImplementation.class;
bind(Service.class).to(serviceType);
}
}
public static void main(String[] args) {
String option = args[0];
Injector injector = Guice.createInjector(new MyRuntimeServiceModule(option));
}

Resources