spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
74.91k stars 40.62k forks source link

throw NoUniqueBeanDefinitionException when using @SpyBean in @SpringBootTest #10381

Closed jojoldu closed 7 years ago

jojoldu commented 7 years ago

Hi.

I'm using Spring Boot 1.5.7. I'm trying to use @SpyBean in @SpringBootTest but I'm still getting a NoUniqueBeanDefinitionException.

@MockBean is fine with @SpringBootTest, @SpyBean is happening Here's the code we tested:

@RunWith (SpringRunner.class)
@SpringBootTest
public class CustomerServiceSpyTest {

    @Autowired
    private CustomerService customerService;

    @MockBean (name = "customerOrderRepository")
    private CustomerOrderRepository customerOrderRepository;

    @SpyBean (name = "httpSession")
    private HttpSession httpSession;

    @Test
    public void saveMyOrderProductCountInSession () throws Exception {

        // given
        Customer customer = new Customer ();

        given (httpSession.getAttribute ("loginUser"))
                .willReturn (customer);

        CustomerOrder order = new CustomerOrder ();
        order.addProduct (new Product ());
        order.addProduct (new Product ());

        given (customerOrderRepository.findAllByCustomer (customer))
                .willReturn (Stream.of (order));

        // when
        customerService.saveMyOrderProductCountInSession ();
        int count = (Integer) httpSession.getAttribute ("orderCount");

        // then
        assertThat (count, is (2));
    }
}
wilkinsona commented 7 years ago

@jojoldu Unfortunately, I can't tell what's happening from the information that you have provided. Can you please provide complete example that we can unzip or git clone that reproduces the problem you are seeing?

jojoldu commented 7 years ago

Thanks for the comment!

Share the Github link here github I think it's because of @SpyBean in HttpSession. HttpSession also uses the actual implementation that I do not implement, but what SpringBoot provides by default. (Is there a reason similar to @SpyBean not working in JpaRepository?)

wilkinsona commented 7 years ago

Thanks. The problem is that HttpSession isn't registered as a bean. The Framework registers a SessionObjectFactory by calling beanFactory.registerResolvableDependency which is "intended for factory/context references that are supposed to be autowirable but are not defined as beans in the factory". Unfortunately, this means the spying on HttpSession doesn't work as you'd like. There's no way for the @SpyBean infrastructure to find and replace the HttpSession bean is there isn't one. This causes it to create an HttpSession bean which, along with the resolvable dependency defined by the Framework, causes the NoUniqueBeanDefinitionException.

There isn't actually a need (that I can see) to use @SpyBean in your test. This will work:

package com.jojoldu.springmockspybean.service;

import com.jojoldu.springmockspybean.domain.customer.Customer;
import com.jojoldu.springmockspybean.domain.order.CustomerOrder;
import com.jojoldu.springmockspybean.domain.order.CustomerOrderRepository;
import com.jojoldu.springmockspybean.domain.product.Product;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;

import javax.servlet.http.HttpSession;
import java.util.stream.Stream;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;

/**
 * Created by jojoldu@gmail.com on 2017. 9. 21.
 * Blog : http://jojoldu.tistory.com
 * Github : https://github.com/jojoldu
 */

@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomerServiceSpyTest {

    @Autowired
    private CustomerService customerService;

    @MockBean (name = "customerOrderRepository")
    private CustomerOrderRepository customerOrderRepository;

    @Autowired
    private HttpSession httpSession;

    @Test
    public void saveMyOrderProductCountInSession () throws Exception {

        // given
        Customer customer = new Customer ();
        httpSession.setAttribute("loginUser", customer);

        CustomerOrder order = new CustomerOrder ();
        order.addProduct (new Product ());
        order.addProduct (new Product ());

        given (customerOrderRepository.findAllByCustomer (customer))
                .willReturn (Stream.of (order));

        // when
        customerService.saveMyOrderProductCountInSession ();
        int count = (Integer) httpSession.getAttribute ("orderCount");

        // then
        assertThat (count, is (2));
    }

}
wilkinsona commented 7 years ago

We should consider adding a note to the documentation stating that the various dependencies registered by WebApplicationContextUtils can't be spied.

jojoldu commented 7 years ago

Thanks for your kind answers and code The code above is a sample code for an introduction to @MockBean and @SpyBean, so I wanted to clarify why!

Learn a lot Thanks again