简体   繁体   中英

Fake ASMX Web Service Call

I built a .NET ASMX web service connecting to an SQL Server database. There is a web service call GetAllQuestions().

 var myService = new SATService();
 var serviceQuestions = myService.GetAllQuestions();

I saved the result of GetAllQuestions to GetAllQuestions.xml in the local application folder

Is there any way to fake the web service call and use the local xml result?

I just want to take the contents of my entire sql table and have the array of objects with correlating property names automatically generated for me just like with LINQ to SQL web services.

Please keep in mind that I am building a standalone Monotouch iPhone application.

Use dependency injection .

//GetSATService returns the fake service during testing     
var myService = GetSATService(); 
var serviceQuestions = myService.GetAllQuestions();

Or, preferably, in the constructor for the object set the SATService field (so the constructor requires the SATService to be set. If you do this, it will be easier to test.

Edit: Sorry, I'll elaborate here. What you have in your code above is a coupled dependency, where your code creates the object it is using. Dependency injection or the Inversion of Control(IOC) pattern, would have you uncouple that dependency. (Or simply, don't call "new" - let something else do that - something you can control outside the consumer.)

There are several ways to do this, and they are shown in the code below (comments explain):

class Program
{
    static void Main(string[] args)
    {
        //ACTUAL usage
        //Setting up the interface injection
        IInjectableFactory.StaticInjectable = new ConcreteInjectable(1);

        //Injecting via the constructor
        EverythingsInjected injected = 
            new EverythingsInjected(new ConcreteInjectable(100));

        //Injecting via the property
        injected.PropertyInjected = new ConcreteInjectable(1000);

        //using the injected items
        injected.PrintInjectables();
        Console.WriteLine();

        //FOR TESTING (normally done in a unit testing framework)
        IInjectableFactory.StaticInjectable = new TestInjectable();
        EverythingsInjected testInjected = 
            new EverythingsInjected(new TestInjectable());
        testInjected.PropertyInjected = new TestInjectable();
        //this would be an assert of some kind
        testInjected.PrintInjectables(); 

        Console.Read();
    }

    //the inteface you want to represent the decoupled class
    public interface IInjectable { void DoSomething(string myStr); }

    //the "real" injectable
    public class ConcreteInjectable : IInjectable
    {
        private int _myId;
        public ConcreteInjectable(int myId) { _myId = myId; }
        public void DoSomething(string myStr)
        {
            Console.WriteLine("Id:{0} Data:{1}", _myId, myStr);
        }
    }

    //the place to get the IInjectable (not in consuming class)
    public static class IInjectableFactory
    {
        public static IInjectable StaticInjectable { get; set; }
    }

    //the consuming class - with three types of injection used
    public class EverythingsInjected
    {
        private IInjectable _interfaceInjected;
        private IInjectable _constructorInjected;
        private IInjectable _propertyInjected;

        //property allows the setting of a different injectable
        public IInjectable PropertyInjected
        {
            get { return _propertyInjected; }
            set { _propertyInjected = value; }
        }

        //constructor requires the loosely coupled injectable
        public EverythingsInjected(IInjectable constructorInjected)
        {
            //have to set the default with property injected
            _propertyInjected = GetIInjectable();

            //retain the constructor injected injectable
            _constructorInjected = constructorInjected;

            //using basic interface injection
            _interfaceInjected = GetIInjectable();
        }

        //retrieves the loosely coupled injectable
        private IInjectable GetIInjectable()
        {
            return IInjectableFactory.StaticInjectable;
        }

        //method that consumes the injectables
        public void PrintInjectables()
        {
            _interfaceInjected.DoSomething("Interface Injected");
            _constructorInjected.DoSomething("Constructor Injected");
            _propertyInjected.DoSomething("PropertyInjected");
        }
    }

    //the "fake" injectable
    public class TestInjectable : IInjectable
    {
        public void DoSomething(string myStr)
        {
            Console.WriteLine("Id:{0} Data:{1}", -10000, myStr + " For TEST");
        }
    }

The above is a complete console program that you can run and play with to see how this works. I tried to keep it simple, but feel free to ask me any questions you have.

Second Edit: From the comments, it became clear that this was an operational need, not a testing need, so in effect it was a cache. Here is some code that will work for the intended purpose. Again, the below code is a full working console program.

class Program
{
    static void Main(string[] args)
    {
        ServiceFactory factory = new ServiceFactory(false);
        //first call hits the webservice
        GetServiceQuestions(factory);
        //hists the cache next time
        GetServiceQuestions(factory);
        //can refresh on demand
        factory.ResetCache = true;
        GetServiceQuestions(factory);
        Console.Read();
    }

    //where the call to the "service" happens
    private static List<Question> GetServiceQuestions(ServiceFactory factory)
    {
        var myFirstService = factory.GetSATService();
        var firstServiceQuestions = myFirstService.GetAllQuestions();
        foreach (Question question in firstServiceQuestions)
        {
            Console.WriteLine(question.Text);
        }
        return firstServiceQuestions;
    }
}

//this stands in place of your xml file
public static class DataStore
{
    public static List<Question> Questions;
}

//a simple question
public struct Question
{
    private string _text;
    public string Text { get { return _text; } }
    public Question(string text)
    {
        _text = text;
    }
}

//the contract for the real and fake "service"
public interface ISATService
{
    List<Question> GetAllQuestions();
}

//hits the webservice and refreshes the store
public class ServiceWrapper : ISATService
{
    public List<Question> GetAllQuestions()
    {
        Console.WriteLine("From WebService");
        //this would be your webservice call
        DataStore.Questions = new List<Question>()
                   {
                       new Question("How do you do?"), 
                       new Question("How is the weather?")
                   };
        //always return from your local datastore
        return DataStore.Questions;
    }
}

//accesses the data store for the questions
public class FakeService : ISATService
{
    public List<Question> GetAllQuestions()
    {
        Console.WriteLine("From Fake Service (cache):");
        return DataStore.Questions;
    }
}

//The object that decides on using the cache or not
public class ServiceFactory
{
    public  bool ResetCache{ get; set;}
    public ServiceFactory(bool resetCache)
    {
        ResetCache = resetCache;
    }
    public ISATService GetSATService()
    {
        if (DataStore.Questions == null || ResetCache)
            return new ServiceWrapper();
        else
            return new FakeService();
    }
}

Hope this helps. Good luck!

when you say fake the call, are you just testing the client side?

you could use fiddler, intercept the request and return the local xml file to the client. No messing around with your client code then.

To elaborate on Audie's answer

Using DI would get you what you want. Very simply you would create an interface that your real object and your mock object both implement

public interface IFoo
{} 

Then you would have your GetSATService method return either a MockSATSerivce or the real SATService object based on your needs.

This is where you would use a DI container (some object that stores interface to concrete type mappings) You would bootstrap the container with the types you want. So, for a unit test, you could contrstruct a mock container that registers the MockSATService as the implementer of the IFoo interface.

Then you would as the container for the concrete type but interface

IFoo mySATService = Container.Resolve<IFoo>();

Then at runtime you would just change out the container so that it bootstraps with the runtime types instead of the mock types but you code would stay the same (Because you are treating everything as IFoo instead SATService)

Does that make sense?

Over time I found that an interesting way to do this is by extracting an interface and creating a wrapper class. This adapts well to a IoC container and also works fine without one.

When testing, create the class passing a fake service. When using it normally, just call the empty constructor, which might simply construct a provider or resolve one using a config file.

    public DataService : IDataService
    {
        private IDataService _provider;

        public DataService()
        {
            _provider = new RealService();
        }

        public DataService(IDataService provider)
        {
            _provider = provider;
        }

        public object GetAllQuestions()
        {
            return _provider.GetAllQuestions();
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM