pchudzik / springmock

alternative spring mocking infrastructure
https://blog.pchudzik.com/201707/springmock-v1/
MIT License
22 stars 4 forks source link

Question about WebMvcTest support #7

Closed tlenczyk closed 7 years ago

tlenczyk commented 7 years ago

Hi Paweł,

Great library. I'm going to use it for mocking dependencies for Spring MVC controller tests. Did you try it with @ WebMvcTest ?

@WebMvcTest(MyController)
class MyControllerTest extends Specification {

  @Autowired
  MockMvc mockMvc

  @AutowiredMock
  MyService myService

}

now i'm having exception:

java.lang.NullPointerException
    at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1612)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:317)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1078)
    at com.pchudzik.springmock.spock.spring.MockAttachingTestExecutionListener.tryToGetBean(MockAttachingTestExecutionListener.java:122)
    at com.pchudzik.springmock.spock.spring.MockAttachingTestExecutionListener.beforeTestMethod(MockAttachingTestExecutionListener.java:62)
    at org.springframework.test.context.TestContextManager.beforeTestMethod(TestContextManager.java:269)

Do I understand correctly that in current version(1.0.0) it is not supported?

pchudzik commented 7 years ago

Hi

Are you sure its related to springmock?

I've just hacked something like this:

@Repository
public class DemoRepository {
    public String message() {
        return "hello";
    }
}

@Service
public class DemoService {
    public final DemoRepository demoRepository;

    public DemoService(DemoRepository demoRepository) {
        this.demoRepository = demoRepository;
    }

    public String getMessage() {
        return demoRepository.message();
    }
}

@RestController
@RequestMapping("/hello")
public class DemoController {
    private final DemoService demoService;

    public DemoController(DemoService demoService) {
        this.demoService = demoService;
    }

    @RequestMapping
    public DemoMessage message() {
        return new DemoMessage(demoService.getMessage());
    }

    public static class DemoMessage {
        private final String message;

        public DemoMessage(String message) {
            this.message = message;
        }

        public String getMessage() {
            return message;
        }
    }
}

and the test:

@WebMvcTest(DemoController)
class DemoControllerTest extends Specification {
    @Autowired
    MockMvc mockMvc

    @AutowiredMock
    DemoService demoService

    def "should work 1"() {
        given:
        demoService.getMessage() >> "mock1"

        when:
        final request = mockMvc.perform(MockMvcRequestBuilders.get("/hello"))

        then:
        request
                .andExpect(status().isOk())
                .andExpect(jsonPath('$.message', is("mock1")))
    }

    def "should work 2"() {
        given:
        demoService.getMessage() >> "mock2"

        when:
        final request = mockMvc.perform(MockMvcRequestBuilders.get("/hello"))

        then:
        request
                .andExpect(status().isOk())
                .andExpect(jsonPath('$.message', is("mock2")))
    }
}

and it works for me :)

dependencies I've used:

    compile('org.springframework.boot:spring-boot-starter-web')

    testCompile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.12'
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile group: 'org.spockframework', name: 'spock-core', version: '1.1-groovy-2.4'
    testCompile group: 'org.spockframework', name: 'spock-spring', version: '1.1-groovy-2.4'
    testCompile group: 'com.pchudzik.springmock', name: 'springmock-spock', version: '1.0.0'

are you using sprock 1.1 and spring-boot 1.4.4 spring 4.3.6?

Can you provide failing test case? If not maybe you'll find few minutes to debug and investigate what's going on inside com.pchudzik.springmock.spock.spring.MockAttachingTestExecutionListener.tryToGetBean (try to evaluate applicationContext.getBean(beanName) is it returning null? can you tell why?)

Maybe you have both junit and spock tests?

tlenczyk commented 7 years ago

Hmm, strange. I will look at this again next week and let you know about details.

tlenczyk commented 7 years ago

I can confirm that it is working on fresh (simplest) spring-boot project. After debugging I've noticed test is failing when trying to obtain bean instance with name "privilegeEvaluator" which is related to Spring Security. After disabling security in test:

@WebMvcTest(controllers = DemoController, secure = false)
class DemoControllerTest extends Specification {

everything works properly.

pchudzik commented 7 years ago

Leaving it open then. Will investigate what's going on.

Does your service by any chance depends on something related to security (some kind of principal, or something which might trigger spring security?) Or in general depends on something? I'm asking because maybe for some reason spring is trying to populate dependencies. Dependency processing should be ignored (SkipSpockBeansPostProcessing) but who knows maybe custom order should be added to this post processor or something. If you feel like it you can debug it and see what's going on and if SkipSpockBeansPostProcessing is called during getBean execution :)

Just out of curiosity can you check if @MockBean from springBoot works? I'm guessing it does :)

pchudzik commented 7 years ago

I've just checked it works just fine with spring security - https://github.com/pchudzik/springmock-spock-with-security-test but I might be missing something there.

@tlenczyk could you please check what you are doing differently in your project so that's not working. If you have time I'd really appreciate PR to springmock-spock-with-security-test which break it :)

For what exactly you are using privilegeEvaluator? Do you have your own implementation of this class? Do you have custom implementation of PermissionEvaluator? Have you checked is @MockBean is working (you can replace AutowiredMock with MockBean and use mockito to mock stuff)?

pchudzik commented 7 years ago

One more idea to check. Instead of walking whole context hierarchy take advantage of doubleregistry and try to use it to find doubles which are registered com.pchudzik.springmock.spock.spring.MockAttachingTestExecutionListener#beforeTestMethod

Don't walk whole context and fetch all definitions. Search for definitions with names already defined in doubleregistry. It should be enough (check if and how FactoryBeans are registered in the double registry).

When scanning only doubles which are registered and known to be mocks then early beans initialization might be avoided (NPE might be avoided if I will not touch security infrastructure while attaching mocks)

pchudzik commented 7 years ago

@tlenczyk please check latest springmock snapshot version. Since I wasn't able to reproduce the issue I'be been guessing what might be causing this issue but I think I might have come up with the solution to avoid it in the first place :)

Please verify and if it is still failing then reopen this.