SitePoint Sponsor

User Tag List

Results 1 to 12 of 12
  1. #1
    SitePoint Addict
    Join Date
    Dec 2007
    Posts
    348
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    unit testing and MVC

    When using unit testing to develop something in an MVC style architecture, what components do you actually test?

    Say if you've got an ArticlesModel, ArticlesController and ArticlesView, do you write seperate tests for these (TestOfArticlesModel, TestOfArticlesController and TestOfArticlesView) or one all-encompassing test TestOfArticles?

    Right now I'm testing them all individually and I seem to be repeating myself a lot, which smacks of inefficiency...

    Any help is appreciated!

  2. #2
    SitePoint Enthusiast
    Join Date
    Oct 2008
    Posts
    42
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Testing often depends on your Use Cases. If your site is heavily UI oriented, I would suggest using something Selenium. If there are a lot of entry points into your controllers, you can write tests that hit the controllers directly outside of an actual web request (possibly use Mock Objects to simulate a web request).

    BUT!!! (That's three exclamation points, my friend.)

    Always write tests for your back-end. You must test your domain model and its behavior, especially if there is complex business logic within your domain classes. Unit tests are the standard for this, and most languages have a Unit test framework out there. You can also use Unit tests for the front-end stuff, like Selenium and to test your controllers. A clean way is to integrate all your tests into one large test suite and run that regularly (even after a trivial refactor, you need to run the tests).

  3. #3
    SitePoint Addict
    Join Date
    Nov 2005
    Location
    Germany
    Posts
    235
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Make sure all model classes are thoroughly unit tested. I found that unit testing controllers is too much hassle for my application, so I (integration) test them together with their associated classed (no mocks). Testing views should be possible with web test frameworks, but you might run into problems if your application uses fancy javascript - at least I did a few years ago. This might have changed.

  4. #4
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by FrlB View Post
    Testing views should be possible with web test frameworks, but you might run into problems if your application uses fancy javascript - at least I did a few years ago. This might have changed.
    Selenium IDE can work wonders.

  5. #5
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi...

    Quote Originally Posted by kyberfabrikken View Post
    Selenium IDE can work wonders.
    Actually, it doesn't solve everything. There are some UI toolkits which break it .

    As for the original question, I do all these types of testing.

    1) Web tests as acceptance tests. That is, before the code is written we grab a spec into a bunch of test cases. This is often done with the stakeholder. E.g....
    PHP Code:
    function testCanAddNoteToAccount() {
        
    $this->nowJuly(12008);
        
    $account $this->accounts()->createAccount(
                    
    'george''secret''George''Peppard',
                    
    'george@ateam.com''The A-Team''11 Apollo Studios',
                    
    'AS1 23K''GB''020 7485 8855''020 7424 8844');
        
    $this->get($this->home() . "admin/accounts/?account=$account");
        
    $this->setField('note''Something fishy about this account');
        
    $this->setField('nickname''me');
        
    $this->click('Add note');
        
    $this->assertText('01/07/2008 by me Something fishy about this account');

    2) We use mock objects to test the controllers. E.g...
    PHP Code:
    function testIfAccountDoesNotExistThenRedirectToCreateAccount() {
        
    $request = new Request(array('account' => 100));
        
    $continuation = new MockContinuation();
        
    $continuation->expectOnce('redirectTo',
                array(
    '*', array('error' => 'Account 100 does not exist')));
        new 
    AccountSummary(
                
    $request, new MockSession(), $continuation,
                new 
    MockAlert(), new MockAccounts());

    3) Undelying model classes are unit and integration tested. E.g...
    PHP Code:
    function testNotesFetchedNewestFirst() {
        
    $this->nowJuly(12008);
        
    $account $this->accounts()->createAccount(
                            
    'fred''secret''Fred''Bloggs''fred@bloggs.com',
                            
    'Bloggsworth''1 Blogg st.''BL1''GB'123456);
        
    $this->accounts()->addNote($account'Something important''Me');
        
    $this->nowJuly(22008);
        
    $this->accounts()->addNote($account'Something more important''Her');
        
    $this->assertEqual($this->accounts()->getNotes($account),  array(
                                array(
    'note' => 'Something more important''account' => $account,
                                      
    'nickname' => 'Her''written' => '2008-07-02 12:00:00'),
                                array(
    'note' => 'Something important''account' => $account,
                                      
    'nickname' => 'Me''written' => '2008-07-01 12:00:00')));

    4) We use web tests as integration. E.g...
    PHP Code:
    function testNicknameRememberedAcrossSessions() {
        
    $this->nowJuly(12008);
        
    $account $this->accounts()->createAccount(
                    
    'george''secret''George''Peppard',
                    
    'george@ateam.com''The A-Team''11 Apollo Studios',
                    
    'AS1 23K''GB''020 7485 8855''020 7424 8844');
        
    $this->get($this->home() . "admin/accounts/?account=$account");
        
    $this->setField('note''Something fishy about this account');
        
    $this->setField('nickname''me');
        
    $this->click('Add note');
        
    $this->restart();
        
    $this->get($this->home() . "admin/accounts/?account=$account");
        
    $this->assertField('nickname''me');

    All the code snippets are from what I am working on right now, warts'n'all.

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  6. #6
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by lastcraft View Post
    Actually, it doesn't solve everything. There are some UI toolkits which break it .
    Really? Which would that be?

  7. #7
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi...

    Quote Originally Posted by kyberfabrikken View Post
    Really? Which would that be?
    Anything that splits a click into mouseUp and mouseDown (for button animations) screws it up nicely. There are a few commercial Javascript UI toolkits that do this, and some Dojo derivatives.

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  8. #8
    SitePoint Addict
    Join Date
    Dec 2007
    Posts
    348
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Marcus, thanks for your answer but what is a (Mock)Continuation in your script?? I've seen it before, does it check for redirections?

  9. #9
    SitePoint Guru
    Join Date
    Nov 2004
    Location
    Plano
    Posts
    643
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    it's already been mentioned, but i figured i'd reiterate.

    unit testing is for the models / business logic.
    functional testing is for controllers.
    integration testing is for the views.

    unit and functional testing can be done with just regular test cases, but integration testing is something that is more easily done from the client-side of your application. something like selenium.

  10. #10
    SitePoint Addict
    Join Date
    Dec 2007
    Posts
    348
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by XtrEM3 View Post
    integration testing is something that is more easily done from the client-side of your application
    does that mean a web test case suffices as an integration test?

    but if a particular controller method issues a redirect, how can this be caught with a unit test versus a web test? is that the expectContinuation method Marcus posted previously? (which I still have no idea what that is .. lol)

  11. #11
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi...

    Quote Originally Posted by old_iron View Post
    (which I still have no idea what that is .. lol)
    Continuation is a class in our micro-framework for this project. It handles all the redirects and meta-refresh stuff. Here it is...
    PHP Code:
    <?php
    require_once(dirname(__FILE__) . '/../environment/environment.php');

    class 
    Continuation {

        function 
    redirectTo($location$get = array()) {
            
    header("Location: $location$this->query($get));
            exit;
        }

        function 
    changePage($path$request) {
            
    $GLOBALS['from_previous_page'] = $request;
            include(
    dirname(__FILE__) . "/../site/www.wordtracker.com/html/$path");
            exit;
        }

        function 
    accessDenied() {
            
    $environment = new Environment();
            
    header('Location: ' $environment->getHome() . '403.html');
            exit;
        }

        function 
    serverError() {
            
    $environment = new Environment();
            
    header('Location: ' $environment->getHome() . '500.html');
            exit(
    1);
        }

        function 
    asLink($location$query = array()) {
            return 
    $location $this->query($query);
        }

        private function 
    query($get) {
            
    $pairs = array();
            foreach (
    $get as $key => $value) {
                
    $pairs[] = $this->asQuery($key$value);
            }
            return (
    count($pairs) > '?' '') . implode('&'$pairs);
        }

        private function 
    asQuery($key$value) {
            if (
    is_array($value)) {
                
    $terms = array();
                foreach (
    $value as $i => $part) {
                    
    $terms[] = $key ."[$i]=" urlencode($part);
                }
                return 
    join('&'$terms);
            }
            return 
    "$key=" urlencode($value);
        }

        static function 
    previous() {
            return 
    $GLOBALS['from_previous_page'];
        }
    }
    ?>
    Obviously we don't want to issue a redirect during a unit test, or we'd never see the test output. So we mock it and pass the mock in instead.
    PHP Code:
    Mock::generate('Continuation');
    ...
    $continuation = new MockContinuation();
    ...
    new 
    AccountSummary(
                
    $request, new MockSession(), $continuation,
                new 
    MockAlert(), new MockAccounts()); 
    Then we just have to assert that the controller attempted to do a redirect at the appropriate time. Hence the...
    PHP Code:
    $continuation->expectOnce('redirectTo',
                array(
    '*', array('error' => 'Account 100 does not exist'))); 
    This will trip a test failure if the redirect was not sent.

    For a controller doing redirects and stuff, the only reasonable integration test is a web test. In general it needn't be though.

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  12. #12
    SitePoint Addict
    Join Date
    Dec 2007
    Posts
    348
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by lastcraft View Post
    PHP Code:
    $continuation->expectOnce('redirectTo',
                array(
    '*', array('error' => 'Account 100 does not exist'))); 
    Ahhh, thanks for the explanation, it is clearer now. The expectOnce is a method taking the method name and the 2nd parameter being an array of params sent to the method (method redirectTo) ?

    I need to read up more on Mocks...


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •