views:

293

answers:

4

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.

+3  A: 

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!

Audie
That sounds great, but can you be more specific? I looked at the wiki page and could not figure out the c# code that I need. What is in GetSATService();
Bryan
super simple way: GetSatService is a protected virtual method which, by default, returns your service. In your test, you can override GetSatService to return your own implementation. Or you can pass in your implementation through your class constructor.
Juliet
@Bryan, I've elaborated above, with a full working program that shows the different types of injection. Ask any questions here and I'll try to help. Mainly, just pick a type of injection that is easy for your particular situation, and you'll then be able to test easily.
Audie
Thank you Audie. It is very interesting learning about injection and injectables. Is there possibly a less verbose way of doing things....I want to use a linq object that correlates to my sql table and since I can save the xml result of linq web service call that gets the entire table, i was hoping to just save it and access it the same way I would by calling the service.
Bryan
It sounds like you are trying to cache the data and make the web service call only once (or perhaps only when necessary). Is this correct? If so I'll post some code that will show you how to do this with injection (it really is simple) and something like a cache. It might take a minute though. Let me know.
Audie
If you could show me an example of caching data, it would be awesome! In this case, I just have a sql table that will not change so I can pull the data at any time. In order to avoid needing a web connection, I am going to include it in my application folder as content. If I use web services and linq, I can make one webservice call ( with one linq statement behind it) and have my data in a type defined array of objects, which is exactly what I want. So I'd like to do the same thing using just as little code or less. I figured it was easiest to use the saved result of my web service call
Bryan
A: 

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.

Stephen Dolier
How would I do that Stephen? What is a fiddler?
Bryan
fiddler is a http proxy/debugger - http://www.fiddler2.com/fiddler2/Install and run. All your http traffic will be listed. Then create an AutoResponder, enter the Url of the web service and select the local file you would like fiddler to return instead.Fiddler will intercept the request for the webserevice and return the local xml file instead.
Stephen Dolier
That sounds like it would work wonderfully in a web situation, but I am building an iPhone app using monotouch.
Bryan
A: 

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?

Foovanadil
Would that mean I have to declare the IFoo class and types? The reason i want to use the web service object is because types and names are already automatically generated based on my sql database.
Bryan
Yes you would have to declare the interface and the concrete types. The auto gen proxy class won't understand how to do that for you.Honestly, I think you are leaning too heavily on the auto gen classes for what you are trying to do. You should have a GetAllQuestions provider object that understands how to switch between WebService and Local file storage as the backing data store. This is where DI shines. You can swap out implementations of a common interface seamlessly.
Foovanadil
A: 

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();
        }
    }
Haas