[SalesForce] Understanding ‘List has no rows for assignment to SObject’ in a trigger creating new record on custom object

I'm having an issue with my Apex Test Class from a trigger. I've used the trigger multiple times in my sandbox but when I try to test it out it fails. The reason for the failure is 'System.QueryException: List has no rows for assignment to SObject' which I have tried to look up but there are so many answers and they don't apply to my certain case. Tried it multiple ways and I am wondering if anybody here has a good answer.

I have two objects, Implementation (custom) and Opportunity. The trigger fires when an Opportunity is set to 'Closed Won'. Below is the trigger:

    trigger CreateImplementation on Opportunity (after update) {

    List<Id> opps = new List<Id>();

    for ( Opportunity opp : Trigger.new ){


        if((opp.StageName == 'Closed Won' ) &&
        (Trigger.oldMap.get(opp.Id).StageName!='Closed Won') ) {

        opps.add(opp.Id);

    }

    Map<Id,Opportunity> oppsMap = new Map<Id,Opportunity>( [SELECT Id, Account.Id, CloseDate, OwnerId, Account.Name from Opportunity where Id IN :opps] );

    List <Implementation__c> impToInsert = new List <Implementation__c> ();

    for( Opportunity oppo : oppsMap.values()){
        Implementation__c imp = new Implementation__c ();

        imp.Account__c = oppo.AccountId;
        imp.Name = oppo.Account.Name +' Implementation';
        imp.Phase__c = 'Phase 0';
        imp.Contract_Date__c = oppo.CloseDate;

        impToInsert.add(imp);
    }

try {
        insert impToInsert;
    } catch (system.Dmlexception e) {
        system.debug (e);
    }
    }
}

And here is the test class

@istest private class CreateImplementation {
    testMethod private static void CreateImplementation () {

        Account acco = new Account(Name = 'Acme Test Account', Type = 'Prospect');
        insert acco;

        Opportunity opp = new Opportunity(Name = 'Acme Test Opp', CloseDate=System.Today(), AccountId=acco.id, Type= 'New Business', Business_Value__c='NA',Closed_Reasons__c='NA', StageName='Closed Won',Amount=1000);
        insert opp;

        Implementation__c imp = [ SELECT Account__c, Name, Phase__c FROM Implementation__c WHERE Account__c = :acco.id LIMIT 1 ];
        System.assertEquals(imp.Phase__c,'Phase 0');
    }
}

Best Answer

Your trigger is only firing after update and your test does not perform any updates, only the insert of an account and opportunity. Do you intend for Implementation records to be inserted on the insert of a new opportunity? If so, you should make your trigger after insert as well. Alternatively, you can insert the opp and then update it in your test.

The likely reason for your List has no row error is that you are trying to SOQL into a single sObject and not a list and this will throw an error when no rows are returned. In this case, no rows are returned from your Implementation query because of the issue with after update and how you've structured your test.

Instead, try querying into a list of sObject and this will prevent an exception from occurring in your test:

List<Implementation__c> impls = [SELECT...
// And assuming you arrange your test to produce 1 object...
System.assertEquals(1, impls.size());
System.assertEquals('Phase 0', impls[0].Phase__c);