One thing I’ve never felt like I’ve done very well on SharePoint List centric projects is plan for or support automated testing scenarios.  You know the kind of project where you build an awesome UI and use one or more SharePoint lists as your database. Often these projects have been so quick moving that there just hasn’t been time or budget for those sorts bells and whistles.

Recently I decided to make time and see if I could come up with a solution.  My goal was fairly simple; I wanted to be able to run my Angular 2.0 app locally or as part of an automated test scenario without SharePoint in the mix at all. Then be able to deploy it to SharePoint and have it run there with no code or configuration changes.

One additional consideration I had was that I am using PnP JS Core to wrap the rest API calls rather than going with straight http.  I’m a huge fan of this library for simplifying common SharePoint operations. I also love the PnP PowerShell Commands. Both are basically in my tool belt at all times.  If you are not using PnP just search for Angular 2 MockBackEnd Examples and you should easily find examples where you can replace Angular Http with one of your own that allows you to mock data.  Although I didn’t follow the example all the way through the post Look Ma, No Server: Developing Apps with Angular 2 Mockbackend from Vildan Softic looked promising.

When using PnP JS Core, the implementation offers a way to have your own Custom HttpClientImpl that you can simply plug in when you setup the PnP configuration specifics for your project.  In the main AppModule of my angular project I typically have a call within the constructor to perform this setup.  In the code below I’m setting the fetchClientFactory to my own custom fetchclient that really is only going to play the role of traffic cop for my scenario.

]
export class AppModule { 

  constructor() {
    this.setupPnp();
  }

  setupPnp(): void {
    let url = getBaseHref();
    pnp.setup(
      {
        baseUrl: url,
        headers: { "Accept": "application/json;odata=verbose" },
        fetchClientFactory: () => {
          return new CustomFetchClient();
        }
      })
  }
}

The customfetchclient is the main reason no configuration is needed to determine if I am running locally or within SharePoint. It simply looks at the Url and if it’s running on localhost it will redirect to the operations workhorse, the MockResponse class, otherwise I simply uses PnP’s fetch client. A key thing to mention here is that unlike the examples provided on Custom HttpClientImpl I decided to inherit their fetchclient rather than fully implement my own based on their interface. I honestly didn’t want to have to redo all that work they accomplish behind the super.fetch call in my code below.

]
import { FetchOptions, FetchClient } from 'sp-pnp-js';
import { MockResponse } from './mocks/mockresponse';

export class CustomFetchClient extends FetchClient {
    fetch(url: string, options: FetchOptions): Promise {
        if (url.startsWith("http://localhost")) {
            return new MockResponse().fetch(url, options);
        }
        else {
            return super.fetch(url, options);
        }
    }
}

MockResponse is where things get messy and in my experiment it’s still not as clean, configurable and magical as I would like it to be. This is version .1 of my code so this is to be expected I guess. However, it works and will just require some “upkeep” for our project to plug in additional mock data and other mocking constructs as needed along the way. Below is the only public method of the class and just deterimes which private method needs to handle the request, do the work and return a mocked response. You’ll notice things such as looking for a post where the url ends with api/contextinfo which is part of the “dance” when posting to SharePoint. I decided it deserved its own method to keep the normal post method more clean and consistent.

]
fetch(url: string, options: FetchOptions): Promise<Response> {
    let response;

    if (options.method.toUpperCase() === "GET") {
        response = this.Get(url);
    }
    else if (options.method.toUpperCase() === "POST" && url.toLowerCase().endsWith("_api/contextinfo")) {
        response = this.PostContextInfo();
    }
    else {
        response = this.Post(url, options);
    }

    return Promise.resolve(response);
}

Since I thought the simplest type of request to mock is was going to be an HTTP GET lets start there. Note I later learned POSTing an item is probably simpler to mock. Before I show the mock code I just wanted to step back and show the service code that is utilizing PnP to issue that get request. The code below is part of a DocumentService injected into a UI component. The method below, getAllDocs, looks EXACTLY as it would have if I had no mock data. It gets a list of items and then loops those items and maps them to internal data models used to bind to the UI. No mock anything here.

]
async getAllDocs(buildParent: boolean): Promise<BaseDocument[]> {
    super.logInfo("getAllDocs");

    let items = await pnp.sp.web.lists.getByTitle(this.listTitle).items
        .get()
        .catch(e => { super.logPnpError(e); return false });

    if (this.documents == null) {
        this.documents = [];
        for (let item of items) {
            this.documents.push(this.mapItem(item));
        }
    }
    return this.documents;
}

When the above code calls pnp.sp.web.lists.getByTitle(“Documents”).items.get() it will go through the customfetchclient and while running locally realize it needs to pass responsibly to MockResponse and ultimately end up in the Get method below. As I’m building this method up, you’ll see that I inspect the url and when a call is going against the Documents library I grab a local copy of an array of items that has been mocked to look like data from that SharePoint list. I then have methods that may “reshape” that array based on any select parameters. For example if $select=ID came in, then that would be the only item property I return for each item in the array. The applyFilter will also attempt to filter the array values based on query params like $filter=Value eq ‘something’ and so on. The applyFilter is where much work still remains for me but I’ve included the start of the pattern in the code below.

private Get(urlString: string): Response {

    let url = parse(urlString, true, true);
    let body: string;
    let items: any[] = [];

    if (urlString.indexOf("getByTitle('Releases')") != -1) {
        items = ReleaseList.Items;
    }
    else if (urlString.indexOf("getByTitle('Projects')") != -1) {
        items = ProjectList.Items;
    }
    else if (urlString.indexOf("getByTitle('Documents')") != -1) {
        items = DocumentList.Items;
    }

    // apply select, filter, top, etc...
    items = this.applyFilter(items, url.query.$filter);
    items = this.applySelect(items, url.query.$select);

    if (url.pathname.endsWith("/items")) {
        body = JSON.stringify(items);
    }
    else if (url.pathname.endsWith(")")) {
        let index = url.pathname.lastIndexOf("(");
        let id = url.pathname.slice(index + 1, url.pathname.length - 1);

        let item = items.find(i => { return i.ID === +id });
        body = JSON.stringify(item);
    }
    else {
        //not sure what might hit here yet
    }

    return new Response(body, { status: 200 });
}

private applySelect(items: any[], select: string): any[] {
    let newItems: any[] = [];
    if (select != null && select.length > 0) {
        let keys = select.split(",");
        for (let item of items) {
            let newItem = {};
            for (let key of keys) {
                newItem[key] = item[key];
            }
            newItems.push(newItem);
        }
        return newItems;
    }
    else {
        return items;
    }
}

private applyFilter(items: any[], filter: string): any[] {
    let newItems: any[] = [];
    if (filter != null && filter.length > 0) {
        //assumes this is always 3 parts currently, will need refactoring for startswith etc.
        // (e.g. ZipLookup/Id eq "1")
        let filterParts = filter.split(" ");  
        let simpleFilter = this.createSimpleFilter(filterParts);

        for (let item of items) {
            let propertyValue = item;
            for(let property of simpleFilter.property){
                propertyValue = propertyValue[property];
            }
            
            let filterValue: any;
            if(typeof(propertyValue) === "number") {
                filterValue = +simpleFilter.value;
            }
            else {
                filterValue = simpleFilter.value;
            }

            let match: boolean = false;
            switch(simpleFilter.operator.toLowerCase()) {
                case "eq":
                    if(propertyValue === filterValue) {
                        match = true;
                    }
                    break;
                default:
                    break;
            }
            
            if(match) {
                newItems.push(item);
            }
        }
        return newItems;
    }
    else {
        return items;
    }
}

private createSimpleFilter(filterParts: string[]) : SimpleFilter {
    let simpleFilter = new SimpleFilter();
    if(filterParts.length == 3) {
        simpleFilter.operator = filterParts[1];
        simpleFilter.value = filterParts[2];

        if(filterParts[0].indexOf("/") != -1) {
            let lookupParts = filterParts[0].split("/");
            for(let part of lookupParts){
                simpleFilter.property.push(part);
            }
        }
        else {
            simpleFilter.property.push(filterParts[0]);
        }
    }

    return simpleFilter;
}

There really isn’t much to the arrays that mock the list items. The are just simple classes I create for each list with a static Items property and then a collection of items. Note that I do use imports to other arrays as a way for me to bind lookups.

]
export class DocumentList {
    static Items: any[] = [
        {
            ID: 1,
            ProjectLookup: ProjectList.Items[0],
            DocumentType: "BR",
            Author: "George",
            Version: 1.0,
            StatusLookup: StatusList.Items[2],
            CreatedDate: "6/10/2017",
            UpdatedDate: "6/11/2017",
            BaselinedDate: "6/12/2017"
        },
...
    ]
}

If you’ve made it this far in the post I’m sure you are wondering about HTTP POST. In some respects, this is easier than the GET because a post is almost always for a single item and there aren’t as many other parameters to account for (filter, select, top, etc…). In this case we just need to take a body and either create an item or update an existing item. Again for completeness my ReleaseService might have a save method like below that is called from the UI component.

]
async save(release: Release) {
    super.logInfo(`save: [id:${release.id}] `);

    if (release.id != null && release.id !== 0) {
        await pnp.sp.web.lists.getByTitle(this.listTitle).items
            .getById(release.id)
            .update({
                Title: release.name
            })
    }
    else {
        await pnp.sp.web.lists.getByTitle(this.listTitle).items
            .add({
                Title: release.name
            })
    }

    this.releases = null;
}

In the MockResponse class the internal Post method handler will first do similar work as the GET just to figure out which list the user is going against and get the appropriate list of mock items to work with. Depending on the url format we can determine if this is new /items or an update /items(1) and add to the array of items or update an existing one.

    let url = parse(urlString, true, true);
    let body: string;
    let items: any[] = [];

    if (urlString.indexOf("getByTitle('Releases')") != -1) {
        items = ReleaseList.Items;
    }
    else if (urlString.indexOf("getByTitle('Projects')") != -1) {
        items = ProjectList.Items;
    }
    else if (urlString.indexOf("getByTitle('Documents')") != -1) {
        items = DocumentList.Items;
    }
    
    if (url.pathname.endsWith("/items")) {
        //add 
        let item: any = {};
        item["ID"] = items.length + 1;
        let requestBody = JSON.parse(options.body);
        Object.keys(requestBody).map(
            (e) => item[e] = requestBody[e]
        );

        items.push(item);

        let result: ItemAddResult = {
            item: item,
            data: {}
        }

        body = JSON.stringify(result);
    }
    else if (url.pathname.endsWith(")")) {
        //update
        let index = url.pathname.lastIndexOf("(");
        let id = url.pathname.slice(index + 1, url.pathname.length - 1);

        let item = items.find(i => { return i.ID === +id });
        let requestBody = JSON.parse(options.body);
        Object.keys(requestBody).map(
            (e) => item[e] = requestBody[e]
        );

        let result: ItemUpdateResult = {
            item: item,
            data: { "odata.etag": "" }
        }

        body = JSON.stringify(result);
    }
    else {
        //not sure what might hit here yet
    }

    return new Response(body, { status: 200 });
}

I hope the code, patterns and approach I’ve began helps someone else as they explore options for mocking SharePoint list data. I’m still a few weeks out from actually starting the project where I plan to use this approach and I’m sure when things GET REAL I will have modifications and refactoring driven by more and more data and GET/POST scenarios. I’ll try to update this post with those lessons and look forward to any thoughts and recommendations others have based on this or other approaches for mocking SharePoint list data.

Share and Enjoy !

Related Content: