POST
|
For what it's worth I came up with a strategy, using the Dependency Inversion Principle and the Facade Pattern, to be able to mock ArcGIS JS API modules and eliminate the HTTP requests being made from loadModules(). I posted in greater detail on my blog here: https://seesharpdotnet.wordpress.com/2020/12/03/angular-and-arcgis-api-for-javascript-a-unit-testing-strategy-using-dependency-injection-and-the-facade-pattern/ A working sample application applying this approach and test coverage is available in this repository: https://github.com/mfcallahan/angular-cli-esri-map-unit-testing Here is the summary from the repo's README file: The problem The esri-loader allows an application to load Dojo AMD Modules outside of the Dojo Toolkit. A module can be lazy loaded, improving the initial load performance of the application by waiting to fetch API resources until they are actually needed: // MapService class to encapsulate ArcGIS API
@Injectable({ providedIn: 'root' })
export class MapService {
mapView?: esri.MapView;
async initDefaultMap(): Promise<void> {
// loadModules() will make HTTP requests to arcgis.com to fetch specified modules
const [Map, MapView] = await loadModules(['esri/Map', 'esri/views/MapView']);
this.mapView = new MapView({
map: new Map({ basemap: 'streets' }),
center: [-112.077, 39.83],
zoom: 5,
});
}
} However, this can make unit testing difficult, as the system under test does not have any reference to the objects in an ArcGIS API module until an HTTP request is made to fetch it. A test for the initDefaultMap() method will call loadModules() and make HTTP requests to arcgis.com to fetch the resources needed. This may not be desirable for a few reasons: The test becomes more like an integration test; we want to assert the component.mapView was correctly set inside loadModules(), not test that the application could connect to the internet and fetch dependencies. Tests may be executed in an environment which may not have access to the ArcGIS CDN, such as an automated build pipeline or server. Tests to ensure error responses from the request to load an ArcGIS API module (the unhappy path) are handled properly may be necessary. // initDefaultMap() unit test
it('should initialize a default map', async () => {
await service.initDefaultMap(); // test will make actual HTTP requests!
expect(service.mapView).not.toBeUndefined();
}); My solution Difficult to mock code is difficult to test! By refactoring the application code to follow the Dependency Inversion Principle and leverage Dependency Injection, the tight coupling between the above initDefaultMap() method and the esri-loader can be eliminated. The Facade Pattern can be used, creating a wrapper class for loadModules() and others methods exported by esri-loader which can then be injected into the class that has a dependencies on ArcGIS API modules. The wrapper class exposes its own loadModules() method which can be easily mocked, eliminating HTTP requests to the ArcGIS CDN in a test suite. A library such as TypeMoq can be used to create mock instances of the various ArcGIS API modules. // Singleton service wrapper class for esri-loader
@Injectable({ providedIn: 'root' })
export class EsriLoaderWrapperService {
constructor() {}
public async loadModules(modules: string[]): Promise<any[]> {
return await loadModules(modules);
}
public getInstance<T>(type: new (paramObj: any) => T, paramObj?: any): T {
return new type(paramObj);
}
}
// Updated MapService class
@Injectable({ providedIn: 'root' })
export class MapService {
mapView?: esri.MapView;
constructor(readonly esriLoaderWrapperService: EsriLoaderWrapperService) {}
async initDefaultMap(): Promise<void> {
const [Map, MapView] = await this.esriLoaderWrapperService.loadModules(['esri/Map', 'esri/views/MapView']);
const map = this.esriLoaderWrapperService.getInstance<esri.Map>(Map, { 'streets' });
this.mapView = this.esriLoaderWrapperService.getInstance<esri.MapView>(MapView, {
map,
center: [-112.077, 39.83],
zoom: 5,
});
}
}
// Updated initDefaultMap() unit test
it('should initialize a default map', async () => {
// Arrange
const mockMap = TypeMoq.Mock.ofType<esri.Map>();
const mockMapView = TypeMoq.Mock.ofType<esri.MapView>();
const esriMockTypes = [mockMap, mockMapView];
const loadModulesSpy = spyOn(service.esriLoaderWrapperService, 'loadModules').and.returnValue(
Promise.resolve(esriMockTypes)
);
const getInstanceSpy = spyOn(service.esriLoaderWrapperService, 'getInstance').and.returnValues(
...esriMockTypes.map((mock) => mock.object)
);
// Act
await service.initDefaultMap(basemap, centerLon, centerLat, zoom, elementRef);
// Assert
expect(loadModulesSpy).toHaveBeenCalledTimes(1);
expect(getInstanceSpy).toHaveBeenCalledTimes(esriMockTypes.length);
expect(service.mapView).not.toBeUndefined();
expect(service.mapView).toBe(mockMapView.object);
});
... View more
12-03-2020
02:50 PM
|
0
|
0
|
133
|
POST
|
A coworker found this article for me which appears to answer the question: FAQ: How frequently is the World Imagery basemap updated? The World Imagery basemap is not collectively updated. Rather, on occasion, updates occur on the different images within the basemap, and there is no actual known cycle for this activity.
... View more
11-06-2020
08:56 AM
|
0
|
0
|
78
|
POST
|
Question in title. I'm wondering how often world basemaps and imagery are updated to include new streets, new labels, new building footprints etc.
... View more
11-04-2020
02:31 PM
|
0
|
1
|
129
|
POST
|
Thanks, adding a similar configuration to my project looks like it would resolve the path issue I was seeing. I ultimately abandoned moving my the project to the arcgis-webpack-plugin, but I will likely refer back to your answer in the near future with other projects. Thanks for taking the time to help!
... View more
01-23-2020
12:19 PM
|
0
|
0
|
186
|
POST
|
Thanks for the reply. Can you elaborate on what is meant by "This let's the API know to load the workers from the CDN, because they cannot currently be built with webpack and I don't see that being done in that app..."
... View more
11-12-2019
01:01 PM
|
0
|
2
|
186
|
POST
|
I've come across an error which I am not quite sure how to diagnose. I have an Angular 7 application which uses the ArcGIS JS API and the arcgis-webpack-plugin, configured similarly to this sample app: GitHub - Esri/angular-cli-esri-map at arcgis-webpack-angular I need to use some components of the dojo framework directly, importing them like this: import * as dojo from 'dojo/dojo' ; import * as domProp from 'dojo/dom-prop' ; However, I'm receiving this error at run time (the app does build without error): DOMException: Failed to execute 'importScripts' on 'WorkerGlobalScope': The script at 'localhost:4200/dojo/arcgis-js-api/views/2d/layers/features/Pipeline.js' failed to load. The error is straightforward - the script at '/dojo/arcgis-js-api/views/2d/layers/features/Pipeline.js' fails to load because it does not exist. But I was wondering if anyone can shed some light on this and help me understand what would cause an app to look in the 'dojo' API files for an ArcGIS JS API file. File '/arcgis-js-api/views/2d/layers/features/Pipeline.js' does exist, I'm just not sure what uses Pipeline.js as that is not something I directly reference or import, and why the app looks inside the dojo API for that file.
... View more
11-06-2019
12:40 PM
|
0
|
6
|
1055
|
POST
|
Thanks, this seems like the best way to go about it. I will experiment with the ArcGIS Webpack plugin and see if this will allow me to use another library to mock out the API classes.
... View more
10-16-2019
02:23 PM
|
0
|
0
|
174
|
POST
|
Some more info: I'm building an Angular 7 application using the esri-loader and I'm really struggling with trying figure out the best way to mock ArcGIS JS API dependencies in my tests. I have come across very little sample code for applications built with frameworks other than dojo that use the ArcGIS JS API and also contain meaningful unit tests. Is anyone out there doing this successfully and able to provide some guidance? For example, say I have a MapComponent class that looks like this: export class MapComponent { mapView : MapView ; public async initializeMap ( lat : number , lon : number , zoom : number ) { const [ Map , MapView ] = await loadModules ( [ 'esri/Map' , 'esri/views/MapView' ] ) ; this . mapView = new MapView ( { zoom : zoom , center : [ lat , lon ] map : new Map ( { basemap : 'streets' } ) } ) ; } public async addPointToMap ( lat : number , lon : number ) { const [ Graphic ] = await loadModules ( [ 'esri/Graphic' ] ) ; const point = { type : "point" , longitude : lon , latitude : lat } ; const markerSymbol = { type : "simple-marker" , color : [ 226 , 119 , 40 ] , outline : { color : [ 255 , 255 , 255 ] , width : 2 } } ; const pointGraphic = new Graphic ( { geometry : point , symbol : markerSymbol } ) ; this . mapView . graphics . add ( pointGraphic ) ; } } I want to test the initializeMap() and addPointToMap() functions, but what is the best way to do this without actually making an http request to get the required API modules and creating concrete instances of each class I need? A test class might look like this: describe ( 'MapComponent' , ( ) = > { let fixture : ComponentFixture < MapComponent > ; let component ; beforeEach ( async ( ) = > { TestBed . configureTestingModule ( { declarations : [ MapComponent ] , } ) . compileComponents ( ) ; fixture = TestBed . createComponent ( MapComponent ) ; component = fixture . debugElement . componentInstance ; } ) ; it ( 'should initialize map' , async ( ) = > { // calling this function will fetch the ArcGIS JS API dependencies // and create concrete instances of Map and MapView objects - need // to mock these await component . initializeMap ( ) ; expect ( component . mapService . mapView ) . toBeTruthy ( ) ; } ) ; it ( 'should add a point to the map' , async ( ) = > { await component . addPointToMap ( 32.7 , - 117.1 ) ; // assert point is created in component.mapService.mapView object } ) ; } ) ; As mentioned above, I'm really struggling with how to mock out ArcGIS JS API classes. When testing my own code, I would implement a class from some interface and use DI to assist with testing, but I'm not seeing how I can test the ArcGIS API classes. Help me interwebs, you're my only hope...
... View more
10-08-2019
04:48 PM
|
0
|
4
|
951
|
POST
|
I came across the "Pre-Summit Hands-On Training" am I'm wondering if I'm able to register for just one of these sessions if I'm not attending the actual dev summit. https://www.esri.com/en-us/about/events/devsummit/agenda/pre-summit-hands-on-training I'm not able to attend the regular summit, but I would be able to attend one of the pre-summit sessions on Sunday and/or Monday if that is possible.
... View more
01-31-2019
03:08 PM
|
1
|
3
|
227
|
Online Status |
Offline
|
Date Last Visited |
Monday
|