How can I launch a fragment with safe args in an instrumentation test? - android-fragments

Below I have a test class designed to launch a fragment in isolation and test the navController's ability to navigate.
The first test, landingToGameFragmentTest() works perfectly!
The second test launches a fragment that depends on safe args to be passed to it. Aside from that, there is no difference I can perceive in how they are executed.
// Declare navController at top level so it can be accessed from any test in the class
private lateinit var navController: TestNavHostController
// Use Generic type with fragment as upper bound to pass any type of FragmentScenario
private fun <T : Fragment> init(scenario: FragmentScenario<T>) {
// Create a test navController
navController = TestNavHostController(
ApplicationProvider.getApplicationContext()
)
scenario.onFragment { fragment ->
// Link navController to its graph
navController.setGraph(R.navigation.nav_graph)
// Link fragment to its navController
Navigation.setViewNavController(fragment.requireView(), navController)
}
}
#Test
fun landingToGameFragmentTest() {
init(launchFragmentInContainer<LandingFragment>(themeResId = THEME))
// Click button to navigate to GameFragment
onView(withId(R.id.button_start_game))
.perform(click())
assertEquals("Navigation to GameFragment failed",
R.id.gameFragment,
navController.currentDestination?.id)
}
#Test
fun gameToLandingFragmentTest() {
init(launchFragmentInContainer<GameFragment>(themeResId = THEME, fragmentArgs = Bundle()))
onView(withId(R.id.button_end_game))
.perform(click())
assertEquals("Navigation to LandingFragment failed",
R.id.landingFragment,
navController.currentDestination?.id)
}
I set a default value for its arguments, but I still got a null arguments exception until I passed it an empty bundle. Now the fragment will launch but it appears not to be able to navigate to any other fragment!
I could find no similar questions on SO, and the stack output is beyond me.
After the init(launchFragmentInContainer()) line I stepped through the code and found this throwing an illegalArgumentException:
public static int parseInt(#RecentlyNonNull String s, int radix) throws NumberFormatException {
throw new RuntimeException("Stub!");
}
Which then leads to getNavigator(), which passes the name "fragment".
However the only navigators are "navigation" and "test", of which I believe it should be test.
The illegalStateException is then thrown:
/**
* Retrieves a registered [Navigator] by name.
*
* #param name name of the navigator to return
* #return the registered navigator with the given name
*
* #throws IllegalStateException if the Navigator has not been added
*
* #see NavigatorProvider.addNavigator
*/
#Suppress("UNCHECKED_CAST")
#CallSuper
public open fun <T : Navigator<*>> getNavigator(name: String): T {
require(validateName(name)) { "navigator name cannot be an empty string" }
val navigator = _navigators[name]
?: throw IllegalStateException(
"Could not find Navigator with name \"$name\". You must call " +
"NavController.addNavigator() for each navigation type."
)
return navigator as T
}
Finally navigate() is called on onView(withId(R.id.button_end_game)) generating:
I likely don't have to keep this test in this specific instance. However, I certainly will need to know how to launch a Fragment in isolation (that depends on safe args) in the future.
Thank you for your consideration!!

You have two entirely separate problems here:
Your Fragment needs to have arguments passed to it.
Arguments are passed to your fragment via the fragmentArgs parameter of launchFragmentInContainer as explained in the Fragment testing guide.
Each Args class, such as your LandingFragmentArgs has a constructor that lets you construct that Args class directly. You can then use the toBundle() method to make the Bundle that you pass to launchFragmentInContainer:
val args = GameFragmentArgs(/* pass in your required args here */)
val bundle = args.toBundle()
init(launchFragmentInContainer<GameFragment>(fragmentArgs = bundle, themeResId = THEME))
Your NavController needs to have its state set to the GameFragment destination
Your TestNavHostController doesn't know that your test needs to start on the destination associated with GameFragment - by default, it will just be on the startDestination of your graph (where whatever action you are trying to trigger doesn't exist).
As per the Test Navigation documentation:
TestNavHostController provides a setCurrentDestination method that allows you to set the current destination so that the NavController is in the correct state before your test begins.
So you need to make sure you call setCurrentDestination after your init call:
val args = GameFragmentArgs(/* pass in your required args here */)
val bundle = args.toBundle()
val scenario = launchFragmentInContainer<GameFragment>(fragmentArgs = bundle, themeResId = THEME)
init(scenario)
// Ensure that the NavController is set to the expected destination
// using the ID from your navigation graph associated with GameFragment
scenario.onFragment {
// Just like setGraph(), this needs to be called on the main thread
navController.setCurrentDestination(R.id.game_fragment, bundle)
}

Related

Inject multiple implementations in abp.io .NET5/6/Core

UPDATE 2: FIXED THE CODE at the end
I have the abp.io service below with 2 parameters in the constructor instantiated via DI.
One of them, IOutcomeWriter, has 2 implementations.
I'd like to define at runtime which of the implementations of IOutcomeWriter to use.
This is the main service:
public class UCManagerService
: DomainService, IUCManagerService, ITransientDependency {
private readonly IUCInputReader _inputReader;
// This field can have 2 or 3 implementations.
private readonly IOutcomeWriter _outcomeWriter;
public UCManagerService(
IUCInputReader inputReader, IOutcomeWriter outcomeWriter) {
_inputReader = inputReader;
_outcomeWriter = outcomeWriter;
}
public async Task ExecuteAsync() {
// start processing the input and generate the output
var input = _inputReader.GetInput());
// do something
// ...
_outcomeWriter.Write(something);
}
}
The main service is registered in the AbpModule together with with IUCInputReader and the 2 implementations of IOutcomeWriter:
[DependsOn(
typeof(SwiftConverterDomainModule),
typeof(AbpAutofacModule) // <= use Autofac in some way (I don't know how)
)]
public class ProgramAppModule : AbpModule {
public override void ConfigureServices(ServiceConfigurationContext context) {
context.Services.AddTransient<IUCManagerService, UCManagerService>();
context.Services.AddTransient<IUCInputReader, UCInputReader>();
// 2 implementations of IOutcomeWriter
context.Services.AddTransient<IOutcomeWriter, OutcomeWriter1>();
context.Services.AddTransient<IOutcomeWriter, OutcomeWriter2>();
}
}
What I would like is to instantiate UCManagerService sometimes with OutcomeWriter1 and sometimes with OutcomeWriter2, according to some values in appsettings.json:
IList<JobSetting> jobsToSet = _configuration.GetSection("Jobs")
.Get<List<JobSetting>>();
foreach (JobSetting jobToSet in jobsToSet) {
// If jobsToSet.SomeValue == 'MyValue1' following line should have to
// require a IUCManagerService using OutcomeWriter1. If it is
// 'MyValue2' it'd use OutcomeWriter2, and so on:
var service = abpApplication.ServiceProvider.GetRequiredService<IUCManagerService>(); // ???
// do something else with service
// ...
}
Finally, if a tomorrow I add an OutcomeWriter3 I would just like to register it in ProgramAppModule.ConfigureServices(...) and of course use a different key in appsettings.json.
If I understand correctly, you need the IOutcomeWriter to differ based on the currently executed job. In other words, that means that you need to dynamically switch the writer based on its context.
The fact that it you need to change it dynamically, it means that is not a problem that can be solved solely using your DI configuration, because DI configurations are best kept static.
Instead, you need to mix and match a few concepts. First of all, you need a way to set the used job in the context. For instance:
// DI configuration
services.AddScoped<JobContext>();
// Execution of a job
using (var scope = abpApplication.ServiceProvider.CreateScope())
{
var context = scope.GetRequiredService<JobContext>();
context.CurrentJob = typeof(MyFirstJob);
var job = scope.GetRequiredService<MyFirstJob>();
var job.Execute();
}
In this example, JobContext is a class that holds the data that is used during the execution of a certain job. It is registered as Scoped to allow this data to be available for multiple classes within the same scope.
Now using this new JobContext, you can build an adapter for IOutcomeWriter that can forward the incoming call to the right implementation based on its injected JobContext. This might look as follows:
public class JobSpecificOutcomeWriter : IOutcomeWriter
{
private readonly JobContext context;
private readonly IList<JobSetting> settings;
private readonly IEnumerable<IOutcomeWriter> writers;
public JobSpecificOutcomeWriter(
JobContext context,
IList<JobSetting> settings,
IEnumerable<IOutcomeWriter> writers)
{
this.context = context;
this.settings = settings;
this.writers = writers;
}
// Implement all IOutcomeWriter methods by forwarding them to the
// CurrentWriter.
object IOutcomeWriter.SomeMethod(object a) =>
this.CurrentWriter.SomeMethod(a);
private IOutcomeWriter CurrentWriter
{
get
{
// TODO: Based on the current context and the settings,
// select the proper outcome writer from the writers list.
}
}
}
When JobSpecificOutcomeWriter is injected into UCManagerService (or any component for that matter), it transparently allows the proper writer to be used, without the consuming class from knowing about this.
The tricky part, actually, is to now configure your DI container correctly using JobSpecificOutcomeWriter. Depending on which DI Container you use, your mileage might vary and with the MS.DI Container, this is actually quite complicated.
services.AddTransient<IOutcomeWriter>(c =>
new JobSpecificOutcomeWriter(
context: c.GetRequiredService<JobContext>(),
settings: jobsToSet,
writers: new IOutcomeWriter[]
{
c.GetRequiredService<MyFirstJob>(),
c.GetRequiredService<MySecondJob>(),
c.GetRequiredService<MyThirdJob>(),
});
services.AddTransient<MyFirstJob>();
services.AddTransient<MySecondJob>();
services.AddTransient<MyThirdJob>();

Kotlin bound callable references inconsistency

Today I was creating unit tests for my Presenter in Android app and I noticed some inconsistency with bound callable references. Is it under development or is it language bug? I found that in kotlin 1.1 bound callable references are supported. But my code fails with kotlin 1.1.2-4.
In my presenter class, tested method reads data from database and dao.getAllItems() method have no parameters while view takes list --> view.showData(List<Item>).
I'm using Mockito, RxJava2 and Room Persistance library.
class ItemsPresenter #Inject constructor(private val itemDao: ItemDao) : Presenter<ItemsView>
{
val TAG = this.javaClass.name!!
private val disposables: CompositeDisposable = CompositeDisposable()
private lateinit var view: ItemsView
override fun onCreate(view: ItemsView)
{
this.view = view
}
override fun onDestroy()
{
disposables.clear()
}
fun onGetItems()
{
Observable.fromCallable(itemDao::getAllItems)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ data -> view.showData(data) },
{ throwable -> view.showLoadingDataError(throwable.localizedMessage) }
)
}
}
I have created test for onGetItems() method
#RunWith(KotlinTestRunner::class)
class ItemsPresenterTest
{
private lateinit var view: ItemsView
private lateinit var dao: ItemDao
private lateinit var presenter: ItemsPresenter
#Before
fun setup()
{
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.io() }
dao = mock(ItemDao::class.java)
view = mock(ItemsView::class.java)
presenter = ItemsPresenter(dao)
}
#Test
fun onGetItemsTest()
{
val list = ArrayList<Item>()
When(dao.getAllItems()).thenReturn(list)
presenter.onCreate(view)
presenter.onGetItems()
verify(dao).getAllItems()
verify(view).showData(list)
}
}
When I have setup as above, the test passes without problems. But when I change line
{ data -> view.showData(data) }
to
{ view::showData }
Then my test fails saying
Wanted but not invoked:
itemsView.showData([]);
Is it language bug? Because vulnerable code compiles fine and runs, it just causes the method to not be invoked at all, without any errors.
To clarify, the same code written in Java 8 works fine, lambda argument is correctly passed into method reference. As you can see in Kotlin bound callable references work fine when used with methods that takes no parameters, otherwise they are not called at all.
You should change
{ data -> view.showData(data) }
to
(view::showData)
to pass in the method reference correctly. This way, with (), you're passing in the method reference as the parameter of the subscribe method.
Using {}, you define a new function with a lambda to be given to the subscribe method.
Writing down
{ view::showData }
is equivalent to
{ it -> view::showData }
which is a function that ignores its parameter, and returns the method reference view::showData.

Is there more neat design for requesting static fields in class successors?

I'm implementing network controller that sends requests to the server with integer command type id and binary serialized block of other command data. Prototype of all commands looks like:
class NetCommand {
public static var typeId; // type must be set in successors!
function new() {
}
public function typeGet():Int {
return Reflect.field(Type.getClass(this), "typeId");
}
}
All this mess in typeGet() function done just for access to the static variables with type ids of all successors. I can't simply write
return typeId;
because statics are not inheritable and this method will return 0 as a value of prototype's variable. Is there any neat solution? Is my solution cross-platform?
Update:
All command classes must be registered in controller class like this:
public function bindResponse(aClass:Class<NetCommand>) {
var typeId = Reflect.field(aClass, "typeId");
mBindResponse.set(typeId, aClass);
}
and then when new command arrives its data passes to the method that find necessary class by command id, creates instance of desired class and passes other data to it:
function onResponse(aTypeId:Int, aData:Dynamic) {
var cmdClass:Class<NetCommand> = mBindResponse.get(aTypeId);
var command:NetCommand = Type.createInstance(cmdClass, []);
command.response(aData); // this must be overriden in successor classes
}
Method typeGet() is used only for targeting outgoing instances and error handling with default behaviour of error command class without creating a heap of classes that differs only by typeId constant. So this method suppreses implementation of the real command id and may be overriden for example.
Why don't you simply make typeId an instance member (not static) ?

Grails, injecting/populating domain object with value from session

In my application many classes have common field 'company'. When application saves that objects, they must be filled with company (there is validation for that). Company is also kept in a session. Now, when I want to use domain class as a command object, company must be already filled or I get validation error. Is there any way to always fill company field, before any validation happens, so that I didn't have to do it manually every time.
(I tried custom data binder, but it does not work when there is no parameter in a request)
You could set the property just before the object is saved, updated or validated using the GORM events beforeInsert, beforeUpdate or beforeValidate.
In your domain you need something like that:
import org.springframework.web.context.request.RequestContextHolder
class Foo {
String company
...
def beforeInsert = {
try {
// add some more error checking (i.e. if there is is no request)
def session = RequestContextHolder.currentRequestAttributes().getSession()
if(session) {
this.company = session.company
}
} catch(Exception e) {
log.error e
}
}
}
If you want to bind a property before the process of binding, You can create a custom BindEventListener and register in the grails-app/conf/spring/resources.groovy
First of all, create your custom BindEventListener
/src/groovy/SessionBinderEventListener.groovy
import org.springframework.beans.MutablePropertyValues
import org.springframework.beans.TypeConverter
class SessionBinderEventListener implements BindEVentListener {
void doBind(Object wrapper, MutablePropertyValues mpv, TypeConverter converter) {
def session = RequestContextHolder.currentRequestAttributes().getSession()
mpv.addPropertyValue("company", session.company)
}
}
Second of all, register your BindEventListener
grails-app/conf/spring/resources.groovy
beans = {
sessionBinderEventListener(SessionBinderEventListener)
}
However, if your domain class does not hold a property called company, you will get InvalidPropertyException. To overcome this issue, create a list of classes which contain a property called company - See details bellow
/src/groovy/SessionBinderEventListener.groovy
import org.springframework.beans.MutablePropertyValues
import org.springframework.beans.TypeConverter
class SessionBinderEventListener implements BindEVentListener {
private List<Class> allowedClasses = [Foo]
void doBind(Object wrapper, MutablePropertyValues mpv, TypeConverter converter) {
if(!(allowedClasses.contains(wrapper.class))) {
return
}
def session = RequestContextHolder.currentRequestAttributes().getSession()
mpv.addPropertyValue("company", session.company)
}
}

flexunit: Parametrized tests

I am trying to run a parametrized tests... Was trying to implement it like it explained here:
http://docs.flexunit.org/index.php?title=Parameterized_Test_Styles
Here is what my test case looking
import org.flexunit.runners.Parameterized;
[RunWith("org.flexunit.runners.Parameterized")]
public class ArrayBasedStackTests
{
[Paremeters]
public static var stackProvider:Array = [new ArrayBasedStack(), new LinkedListBasedStack()] ;
private var _stack:IStack;
public function ArrayBasedStackTests(param:IStack)
{
_stack = param;
}
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[Test ( description = "Checks isEmpty method of the stack. For empty stack", dataProvider="stackProvider" )]
public function isEmptyStackPositiveTest():void
{
var stack:IStack = _stack;
assertEquals( true, stack.isEmpty() );
}
But this code throws following initializing Error:
Error: Custom runner class org.flexunit.runners.Parameterized should
be linked into project and implement IRunner. Further it needs to have
a constructor which either just accepts the class, or the class and a
builder.
Need help to fix it
UPDATE
I've updated the code so it looks like this
[RunWith("org.flexunit.runners.Parameterized")]
public class ArrayBasedStackTests
{
private var foo:Parameterized;
[Parameters]
public static function stacks():Array
{
return [ [new ArrayBasedStack()], [new LinkedListBasedStack()] ] ;
}
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[Test ( description = "Checks isEmpty method of the stack. For empty stack", dataProvider="stacks")]
public function isEmptyStackPositiveTest(stack:IStack):void
{
assertEquals( true, _stack.isEmpty() );
}
It works. But the result is a bit strange. I have 4 test executed instead of 2. (I have 2 items in data provider, so cant get why do I have 4 tests).
Output
http://screencast.com/t/G8DHbcjDUkJ
The [Parameters] meta-data specifies that the parameters are passed to the constructor of the test - so the test class is called for each parameter. You also have the dataProvider set for the specific test method, so the test method is also called once for each parameter. Two calls for the test, and two calls to the method, ends up running four tests.
The solution is to either use [Parameters] meta-tag which specifies the data to use for the whole test class, or use the dataProvider for each test method, but not both with the same data at the same time.
You're missing the static reference to Paramaterized, as shown here:
import org.flexunit.runners.Parameterized;
[RunWith("org.flexunit.runners.Parameterized")]
public class MyTestNGTest
{
private var foo:Parameterized;
...
Basically, that error means that the [Runner] defined isn't available at runtime, which occurs if there is no static reference in the class to cause it to get linked in.
In FlexUnit 4.5.1, this approach changed to using [Rule]'s like so:
public class MyTestNGTest
{
[Rule]
public function paramaterizedRule:ParamaterizedRule = new ParamaterizedRule();
...
}
However, I can't seem to see an actual implementation of IMethodRule for paramaterized tests (that example is fictional).

Resources