Unit testing: NoSuchMethodError at mock retrofit get request - android

When I run only method testInsert() test completes with no problem, but when I run a whole class I've got an error:
java.lang.NoSuchMethodError: com.dataart.kvarivoda.myapplication.api.PlacesApi.getNearbyPlaces(Ljava/lang/String;Ljava/lang/String;I)Lretrofit2/Call;
at GetPlacesTest.testLogin(GetPlacesTest.java:61)
....
Retrofit #GET interface is:
public interface PlacesApi {
#GET("/maps/api/place/nearbysearch/json")
PlacesResults getNearbyPlaces(#Query("key") String key, #Query("location") String location, #Query("radius") int radius);
}
My test class is:
public class GetPlacesTest extends ProviderTestCase2 {
PlacesApi mockApi;
EventBus mockEventBus;
PlacesApi api;
Cursor cursor;
String webApiKey;
public GetPlacesTest() {
super(PlacesContentProvider.class, "com.example.myName.myapplication.database.PROVE");
}
public void setUp() throws Exception {
super.setUp();
setContext(InstrumentationRegistry.getTargetContext());
//MockitoAnnotations.initMocks(this);
mockApi = Mockito.mock(PlacesApi.class);
mockEventBus = Mockito.mock(EventBus.class);
webApiKey = getContext().getResources().getString(R.string.webApiKey);
api = ((PlacesApp) getApplication()).getApi();
}
#Override
protected void tearDown() throws Exception {
super.tearDown();
if (cursor != null) {
cursor.close();
}
}
#Test
public void testInsert() throws Exception {
PlacesResults results1 = loadResults("getplaces.json");
Mockito.when(mockApi.getNearbyPlaces(eq("testkey"), Matchers.anyString(), Matchers.anyInt())).thenReturn(results1);
GetPlacesAction action = new GetPlacesAction(getContext().getContentResolver(), mockEventBus, mockApi, "testkey");
action.downloadPlaces();
//check
cursor = getContext().getContentResolver().query(PlacesContentProvider.getUri(DB.PlaceTable.DB_TABLE), null, null, null, null);
assertEquals(2, cursor.getCount());
cursor.moveToPosition(0);
assertEquals("461d123aeb1c1648abdd5e535989d2bc518cf28e", getColumn(DB.PlaceTable.COLUMN_ID));
assertEquals("Astral Tower & Residences", getColumn(DB.PlaceTable.COLUMN_TITLE));
assertEquals(getImageUrl("CoQBcwAAAMUTbLLt7doNLiVSnpGryeIJLVdrDnMPcqs3uV84zfvDklrBr1uYxitVMEZWzTD40xkM923ak8HfRtoGiNdi32mqzP6sKB3lOYNbbOQeaHZ3bStClwhWO3507ryh4bODvEfXc-l42r7rFXFAg9GLSd7N2tqoOgLwzTLray0d1sixEhAZaZ2_ajvBieZvUuPA72d7GhQTtFtpqT8j7UBYSHvq9AuRsoRSig"), getColumn(DB.PlaceTable.COLUMN_IMAGE));
assertEquals(locationToString(-33.868111), getColumn(DB.PlaceTable.COLUMN_LOCATION_LAT));
assertEquals(locationToString(151.195219), getColumn(DB.PlaceTable.COLUMN_LOCATION_LNG));
}
private PlacesResults loadResults(String file) throws IOException {
InputStream is = InstrumentationRegistry.getContext().getAssets().open(file);
return new GsonBuilder().create().fromJson(new InputStreamReader(is), PlacesResults.class);
}
private String getImageUrl(String photoReference) {
return new GoogleImagesUtil("testkey").getImageUrl(photoReference);
}
private String locationToString(double location) {
return String.valueOf(LocationsUtil.locationToInteger(location));
}
private String getColumn(String column) {
return cursor.getString(cursor.getColumnIndex(column));
}
private int getColumnInt(String column) {
return cursor.getInt(cursor.getColumnIndex(column));
}
}

After some trying I couldn't even compile and started to get “No tests found” error. Later I've found that there was some run configuration on the class, although I didn't add anything. I deleted run configuration following this answer:
https://stackoverflow.com/a/38190125/2574228
All the errors gone after that.

Related

Listener not called in Unit Test

I tried to create unit test for my project.
Inside my unit test class I have two methods testCalculate() and testLogin(). Method testCalculate() running fine, meaning that test passed and I got correct testing result.
But problem is in testLogin(), I expect that my code will be print something inside the listener, but it never printed.Meaning that I never get this line
System.out.println("Login success ======= " + response.getResponseObject());
My login method that I want to test itself running fine, meaning if I use it inside my real app, it will login successfully and return some datas that I got from server.
Kindly advise what is possible cause that make my listener is not working in unit test. Really appreciate for any kind help.
#RunWith(RobolectricTestRunner.class)
#Config(manifest = Config.NONE)
public class LoginManagerTest {
private static final String TAG = LoginManagerTest.class.getCanonicalName();
private String usernameStr = "androidtest#gmail.com";
private String passwordStr = "********";
private LoginManager loginManager;
#Test
public void testLogin() throws Exception {
ResponseHandlerListener<String> listener = new ResponseHandlerListener<String>() {
#Override
public void onReceive(AxonResponse<String> response) {
System.out.println("Login success ======= " + response.getResponseObject());
String loginSuccess = Constants.LOGIN_SUCCESS;
assertEquals(TAG, loginSuccess, response.getResponseObject());
}
};
Context context = Robolectric.getShadowApplication().getApplicationContext();
loginManager = new LoginManager(context);
loginManager.login(usernameStr, passwordStr, null, listener);
}
#Test
public void testCalculate() throws Exception {
Context context = Robolectric.getShadowApplication().getApplicationContext();
loginManager = new LoginManager(context);
int num = 5;
int sum = loginManager.calculate(num);
System.out.println("sum = " + sum);
assertEquals(10, sum);
}
}
I had the same issue. I solved by using Robolectric.flushForegroundThreadScheduler(). This will run queued tasks that is on forground thread.
It's going to be like this:
#Test
public void testLogin() throws Exception {
ResponseHandlerListener<String> listener = new ResponseHandlerListener<String>() {
#Override
public void onReceive(AxonResponse<String> response) {
System.out.println("Login success ======= " + response.getResponseObject());
String loginSuccess = Constants.LOGIN_SUCCESS;
assertEquals(TAG, loginSuccess, response.getResponseObject());
}
};
Context context = Robolectric.getShadowApplication().getApplicationContext();
loginManager = new LoginManager(context);
loginManager.login(usernameStr, passwordStr, null, listener);
ThreadSleep(500); //Wait for login process to be executed
Robolectric.flushForegroundThreadScheduler(); // this will run all the pending queue on Main Thread
}
I think the problem is the callback will be called after time, it means run asynchronus, and the test is out of time with other (main) thread. I have no experience about Robolectric test, but a little bit about AndroidTestSuite. So, I suggest you need some waitting time before the listener callback is called. Here is the sample and the other idea. Hope that help.
Update:
Try the below idea (Not tested):
private Object lock = new Object();
#Test
public void testLogin() throws Exception {
ResponseHandlerListener<String> listener = new ResponseHandlerListener<String>() {
#Override
public void onReceive(AxonResponse<String> response) {
System.out.println("Login success ======= " + response.getResponseObject());
String loginSuccess = Constants.LOGIN_SUCCESS;
assertEquals(TAG, loginSuccess, response.getResponseObject());
synchronized (lock) {
lock.notifyAll(); // Will wake up lock.wait()
}
}
};
synchronized (lock) {
Context context = Robolectric.getShadowApplication().getApplicationContext();
loginManager = new LoginManager(context);
loginManager.login(usernameStr, passwordStr, null, listener);
lock.wait();
}
}

Android NPE running JUnit test

I'm trying to implement some tests in my application. One thing that I want to test is writing a java object to my db, then retrieving it and asserting the the object that comes out of the db matches the object that went in.
Here's my MySQLiteHelper application code:
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.concurrent.atomic.AtomicInteger;
class MySQLiteHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "unittesttester.db";
private static final int DATABASE_VERSION = 8;
private static final String LOG_TAG = MySQLiteHelper.class.getSimpleName();
private static final int WEATHER_STALENESS_PERIOD_MS = 60 * 5 * 1000; //5 minutes
private AtomicInteger mOpenCounter = new AtomicInteger();
private static MySQLiteHelper mInstance = null;
private SQLiteDatabase db;
private Context mContext;
public static MySQLiteHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new MySQLiteHelper(context.getApplicationContext());
}
return mInstance;
}
private MySQLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(WeatherTable.CREATE_TABLE_WEATHER);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (newVersion <= DATABASE_VERSION) {
onCreate(db);
}
}
private synchronized SQLiteDatabase openDatabase() {
final int i = mOpenCounter.incrementAndGet();
if (i == 1) {
db = getWritableDatabase();
}
return db;
}
private synchronized void closeDatabase() {
final int i = mOpenCounter.decrementAndGet();
if (i == 0) {
db.close();
}
}
private void truncateWeatherTable() {
db = openDatabase();
db.delete(WeatherTable.TABLE_WEATHER, null, null);
closeDatabase();
}
public void deleteAndInsertWeather(Weather weather) {
db = openDatabase();
db.beginTransaction();
try {
truncateWeatherTable();
insertWeather(weather);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
closeDatabase();
}
}
private void insertWeather(Weather weather) {
db = openDatabase();
db.insert(WeatherTable.TABLE_WEATHER, null, makeWeatherCv(weather));
closeDatabase();
}
public Weather getWeather() {
db = openDatabase();
String sql = "SELECT * FROM " + WeatherTable.TABLE_WEATHER;
Cursor c = null;
Weather weather = null;
try {
c = db.rawQuery(sql, null);
if (c.moveToFirst()) {
weather = makeWeather(c);
//If sample too old return null
if (System.currentTimeMillis() - weather.getTimestamp() > WEATHER_STALENESS_PERIOD_MS) {
weather = null;
truncateWeatherTable();
}
}
} finally {
if (c != null) {
c.close();
}
closeDatabase();
}
return weather;
}
private Weather makeWeather(Cursor c) {
Weather weather = new Weather();
weather.setTimestamp(c.getLong(c.getColumnIndex(WeatherTable.COLUMN_TIMESTAMP)));
weather.setElevation(c.getDouble(c.getColumnIndex(WeatherTable.COLUMN_ELEVATION)));
weather.setTemperature(c.getDouble(c.getColumnIndex(WeatherTable.COLUMN_TEMPERATURE)));
weather.setDusk(c.getInt(c.getColumnIndex(WeatherTable.COLUMN_DUSK)));
weather.setNighttime(c.getInt(c.getColumnIndex(WeatherTable.COLUMN_NIGHTTIME)));
weather.setGravity(c.getDouble(c.getColumnIndex(WeatherTable.COLUMN_GRAVITY)));
weather.setDaytime(c.getInt(c.getColumnIndex(WeatherTable.COLUMN_DAYTIME)));
weather.setHumidity(c.getDouble(c.getColumnIndex(WeatherTable.COLUMN_HUMIDITY)));
weather.setPressure(c.getDouble(c.getColumnIndex(WeatherTable.COLUMN_PRESSURE)));
weather.setOkta(c.getDouble(c.getColumnIndex(WeatherTable.COLUMN_OKTA)));
weather.setDawn(c.getInt(c.getColumnIndex(WeatherTable.COLUMN_DAWN)));
return weather;
}
private ContentValues makeWeatherCv(Weather weather) {
ContentValues contentValues = new ContentValues();
contentValues.put(WeatherTable.COLUMN_TIMESTAMP, weather.getTimestamp());
contentValues.put(WeatherTable.COLUMN_TEMPERATURE, weather.getElevation());
contentValues.put(WeatherTable.COLUMN_TEMPERATURE, weather.getTemperature());
contentValues.put(WeatherTable.COLUMN_DUSK, weather.getDusk());
contentValues.put(WeatherTable.COLUMN_NIGHTTIME, weather.getNighttime());
contentValues.put(WeatherTable.COLUMN_GRAVITY, weather.getGravity());
contentValues.put(WeatherTable.COLUMN_DAYTIME, weather.getDaytime());
contentValues.put(WeatherTable.COLUMN_HUMIDITY, weather.getHumidity());
contentValues.put(WeatherTable.COLUMN_PRESSURE, weather.getPressure());
contentValues.put(WeatherTable.COLUMN_OKTA, weather.getOkta());
contentValues.put(WeatherTable.COLUMN_DAWN, weather.getDawn());
return contentValues;
}
}
Here's my test class for the class above:
import android.test.AndroidTestCase;
import android.test.RenamingDelegatingContext;
import org.junit.Test;
import static org.mockito.Mockito.*;
public class MySQLiteHelperTest extends AndroidTestCase {
private MySQLiteHelper db;
private Weather mockedWeather = mock(Weather.class);
#Override
public void setUp() throws Exception {
super.setUp();
context = new MockContext();
setContext(context);
assertNotNull(context);
RenamingDelegatingContext renamingContext = new RenamingDelegatingContext(getContext(), "test_");
db = MySQLiteHelper.getInstance(renamingContext);
assertNotNull(db);
when(mockedWeather.getDawn()).thenReturn(0);
when(mockedWeather.getDaytime()).thenReturn(1);
when(mockedWeather.getDusk()).thenReturn(2);
when(mockedWeather.getElevation()).thenReturn(3.0);
when(mockedWeather.getGravity()).thenReturn(4.0);
when(mockedWeather.getHumidity()).thenReturn(5.0);
when(mockedWeather.getNighttime()).thenReturn(6);
when(mockedWeather.getOkta()).thenReturn(7.0);
when(mockedWeather.getPressure()).thenReturn(8.0);
when(mockedWeather.getTemperature()).thenReturn(9.0);
when(mockedWeather.getTimestamp()).thenReturn(10L);
}
#Override
public void tearDown() throws Exception {
super.tearDown();
}
public void testGetInstance() throws Exception {
}
public void testOnCreate() throws Exception {
}
public void testOnUpgrade() throws Exception {
}
#Test
public void testDeleteAndInsertWeather() throws Exception {
db.deleteAndInsertWeather(mockedWeather);
Weather actualWeather = db.getWeather();
assertEquals(mockedWeather.getDawn(), actualWeather.getDawn());
assertEquals(mockedWeather.getDaytime(), actualWeather.getDaytime());
assertEquals(mockedWeather.getDusk(), actualWeather.getDusk());
assertEquals(mockedWeather.getElevation(), actualWeather.getElevation());
assertEquals(mockedWeather.getGravity(), actualWeather.getGravity());
assertEquals(mockedWeather.getHumidity(), actualWeather.getHumidity());
assertEquals(mockedWeather.getNighttime(), actualWeather.getNighttime());
assertEquals(mockedWeather.getOkta(), actualWeather.getOkta());
assertEquals(mockedWeather.getPressure(), actualWeather.getPressure());
assertEquals(mockedWeather.getTemperature(), actualWeather.getTemperature());
assertEquals(mockedWeather.getTimestamp(), actualWeather.getTimestamp());
}
public void testDeleteWeather() throws Exception {
}
public void testInsertWeather() throws Exception {
}
public void testGetWeather() throws Exception {
}
public void testWeatherMakeCv() throws Exception {
}
}
When I run the test I am getting a NPE during my test. It seems to occur when the MySQLiteHelper class has its db = getWritableDatabase() line. getWriteableDatabase() is a public method from the base class.
I don't think I understand why this test results in an NPE. In my test I call the static method, MySQLiteHelper.getInstance(Context context) which should initialize the class. It is my assumption that calling getInstance will provide me with a fully initialized instance of MySQLiteHelper. Why does this not seem to be happening?
EDIT:
The problem I have now is that when getWritableDatabase() is called it returns null instead of an instance of SQLiteDatabase.
I ended completing my goals of unit testing my sqlite database. The problem seemed to be that I needed to use the build artifact called Android Instrumentation Test instead of the Unit Test build artifact.
I setup a test class in my app/src/androidTest/java directory. The test class extended InstrumentationTestCase.
When I setup my database I use the context provided by getInstrumentation().getTargetContext(). This was important because originally I tried to use getInstrumentation().getContext() and I found that that would always result in a SQLiteCantOpenDatabaseException.
So it seemed my problems occurred because:
1) I wasn't using the correct test artifact
2) I wasn't using the correct test base class
3) I wasn't getting the context correctly
AndroidTestCase#getContext() returns whatever Context you've set with setContext() and you haven't set anything, so a null is returned`.
Using a null context with SQLiteOpenHelper will NPE when the database is being opened e.g. with getWritableDatabase().
See Getting context in AndroidTestCase or InstrumentationTestCase in Android Studio's Unit Test feature for more details on how to set up a Contex in test cases.

How to test asyc retrofit calls in Android unit tests?

I am struggling to write unit tests for my API implementation in Android. I would like to test the Retrofit functionality but run into concurrency problems, where I do not know how to ensure that the async API calls get executed and finish before I start testing the Android internal database calls.
Here is my test function:
public class postPrintModeTest extends ActivityInstrumentationTestCase2<MainActivity> implements IConstants {
public MainActivity activity;
public postPrintModeTest() {
super(MainActivity.class);
}
#Override
protected void setUp() throws Exception {
super.setUp();
activity = getActivity();
String printModeName = "LSD Mode";
int parentId = 4;
Map<String, Object> payload = new HashMap<String, Object>();
payload.put("name", printModeName);
payload.put("parentId", parentId);
APIExec.getInstance().postPrintMode(IConstants.authorization, IConstants.userId, IConstants.deviceUid, payload); // <- this needs to finish before I execute the tests, so I have proper data in the database.
}
#SmallTest
public void testPrintModeCreated() {
DBPrintMode printMode = APIDBOps.getInstance().readPrintModeByPrintModeID(6);
assertNotNull("Print Mode does not exist", printMode);
}
#SmallTest
public void testPrintModeName() {
DBPrintMode printMode = APIDBOps.getInstance().readPrintModeByPrintModeID(6);
if(printMode != null)
{
assertTrue("Print Mode name is not correct", printMode.getName().equals("LSD Mode"));
}
}
}
and here is the async method in question:
public void postPrintMode(String authorization, final int userid, String deviceuid, final Map payload){
api.postPrintMode(authorization, userid, deviceuid, payload, new Callback<PrintMode>() {
#Override
public void success(PrintMode printMode, Response response) {
if (printMode.get_id() != 0) {
dbOps.writePrintMode(userid, printMode);
bus.getBus().post(new EVTNewPrintMode(printMode));
}
}
#Override
public void failure(RetrofitError retrofitError) {
retrofitError.printStackTrace();
APIUtils.showAPIResponseBody(retrofitError);
}
});
}

How use Roboelectric and Fest to test an android app with native binaries?

I'm trying to setup roboelectric and fest in my own project. However when I try to run ./gradlew clean test in the command line I get the following errors in the test report:
http://pastebin.com/5gaJgftf
My project does build the app without errors though. I only get this issue when I try to run tests, so it seems that Roboelectric is not aware is not aware of my native sqlcipher binaries and other binaries.
So I tried loading it with a shadow class for the runner that loads up the necessary binaries:
#Config(emulateSdk = 18, shadows={MyJniClass.class})
#RunWith(RobolectricTestRunner.class)
public class MainActivityBuildTest {
#Test
public void testSomething() throws Exception {
Activity activity = Robolectric.buildActivity(MainActivity.class).create().get();
assertTrue(activity != null);
}
}
Using my custom jniloader shadow class
#Implements(RobolectricTestRunner.class)
class MyJniClass {
static {
try {
System.loadLibrary("libdatabase_sqlcipher");
System.loadLibrary("libdatabase_android");
System.loadLibrary("libstlport_shared");
} catch (UnsatisfiedLinkError e) {
// only ignore exception in non-android env
if ("Dalvik".equals(System.getProperty("java.vm.name"))) throw e;
}
}
}
You have issues to use sql cipher with robolectric?
My workaround is to use two different implementation of the SQLiteOpenHelper. One use sqlcipher and the another one the default database implementation. This both are behind a factory class, which create the SQLiteDatabase based on a static boolean flag, so the unscure database handling will be eliminated from progard.
The next issue is that both have different SQLiteDatabase classes. So again build a wrapper around the SQLiteDatabase which will be created with the right SQLiteDatabase from the SQLiteOpenHelper Wrapper. Take the Cipher variant as your base. you can ignore methods which exist at default SQLiteDatabase but not at the cipher variant. This wrapper class take the same static boolean flag to choose which database should be used. if make a mistake and take the wrong database then it should throw a null pointer exception ;)
in your app code you should now use only the wrapper classes.
example for DatabaseHelper wrapper
public class MyDatabaseHelper {
public static final String DATABASE_NAME = "my.db";
public static final int DATABASE_VERSION = 1;
MyEncryptedDatabaseHelper encryptedDatabase;
MyUnsecureDatabaseHelper unsecureDatabase;
public MyDatabaseHelper(Context context) {
if (ReleaseControl.USE_UNSECURE_DATABASE) {
unsecureDatabase = new MyUnsecureDatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
return;
}
encryptedDatabase = new MyEncryptedDatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public MySQLiteDatabase getWritableDatabase(String password) throws MySQLiteException {
if (ReleaseControl.USE_UNSECURE_DATABASE) {
try {
return new MySQLiteDatabase(unsecureDatabase.getWritableDatabase());
} catch (android.database.SQLException e) {
throw new MySQLiteException(e);
}
}
try {
return new MySQLiteDatabase(encryptedDatabase.getWritableDatabase(password));
} catch (net.sqlcipher.database.SQLiteException e) {
throw new MySQLiteException(e);
}
}
}
and short snippet from SQLiteDatabase wrapper
public class MySQLiteDatabase {
private net.sqlcipher.database.SQLiteDatabase encryptedDatabase;
private android.database.sqlite.SQLiteDatabase unsecureDatabase;
public MySQLiteDatabase(SQLiteDatabase database) {
encryptedDatabase = database;
}
public MySQLiteDatabase(android.database.sqlite.SQLiteDatabase database) {
unsecureDatabase = database;
}
public static void loadLibs(android.content.Context context) {
if (ReleaseControl.USE_UNSECURE_DATABASE) { return; }
SQLiteDatabase.loadLibs(context);
}
public static int releaseMemory() {
if (ReleaseControl.USE_UNSECURE_DATABASE) {
return android.database.sqlite.SQLiteDatabase.releaseMemory();
}
return net.sqlcipher.database.SQLiteDatabase.releaseMemory();
}
public static SQLiteDatabase openDatabase(String path, String password, MyCursorFactory factory, int flags) {
if(factory == null) factory = new NullCursorFactory();
if (ReleaseControl.USE_UNSECURE_DATABASE) {
return new MySQLiteDatabase(android.database.sqlite.SQLiteDatabase.openDatabase(path, factory.getUnsecure(), flags));
}
return new MySQLiteDatabase(net.sqlcipher.database.SQLiteDatabase.openDatabase(path, password, factory.getEncrypted(), flags));
}
In robolectric test i set the USE_UNSECURE_DATABASE per reflection true

Android Provider Testing

I am trying to test android content providers. I am writing code similar to given here, inserting into a db and then querying the db,
but sometimes and not always, the returned cursor has 0 rows. What may be the problem?
public class MyProviderTestCase extends ProviderTestCase2<MyProvider> {
private MockContentResolver mMockResolver;
....
public MyProviderTestCase() {
super(MyProvider.class, "com.example.MyProvider");
}
protected void setUp() throws Exception {
super.setUp();
mMockResolver = getMockContentResolver();
}
protected void tearDown() throws Exception {
super.tearDown();
}
#SmallTest
public void testInsertAndUpdate() {
...
SomeObject someObject = ...;
Uri insertedUri = mMockResolver.insert(MyrProvider.URI, someObject.createContentValues());
assertNotNull(insertedUri);
Log.d("test insert and update and delete:inserteduri", insertedUri+"");
Cursor c = mMockResolver.query(MyProvider.URI, null, null, null, null);
assertNotNull(c);
assertTrue(c.moveToFirst());// This assertion fails sometimes
}
}

Categories

Resources